This commit is contained in:
parent
f4271b4052
commit
17735eada3
9 changed files with 302 additions and 19 deletions
|
|
@ -6,14 +6,18 @@
|
|||
|
||||
* [PostIt] Une page d'historique des commandes billing permet maintenant d'ouvrir une commande existante.
|
||||
* [PostIt] Une vue "Demandes en cours" en lecture seule est disponible pour le performer, filtrée sur les statuts actifs (Inserted, Accepted, InProgress).
|
||||
* [Yavsc.Org] Nouvelles entités `Country` et `PerformerCodeInputValidation` pour piloter la validation du code entreprise performer par pays.
|
||||
|
||||
### Changed
|
||||
|
||||
* [PostIt] La page détail billing se préremplit depuis une commande existante (Rdv, Brush, MBrush) et passe en mode mise à jour.
|
||||
* [Yavsc.Org] Le formulaire `Manage/SetActivity` inclut désormais le pays d'exercice (`fr`, `en`, `pt`) et applique la regex associée au champ `SIREN`.
|
||||
* [Yavsc.Org] La vérification externe du numéro d'entreprise est conservée uniquement pour le pays `fr`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* [PostIt] Le flux historique n'est plus limité à une simple liste: l'action d'ouverture charge la commande cible puis navigue vers la page détail.
|
||||
* [Yavsc.Org] Le champ `SIREN` n'est plus validé avec une règle unique indépendante du pays d'exercice.
|
||||
|
||||
## [1.0.8-rc9] - unstable
|
||||
|
||||
|
|
|
|||
19
src/Yavsc.Abstract/Workflow/Country.cs
Normal file
19
src/Yavsc.Abstract/Workflow/Country.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Yavsc.Models.Workflow
|
||||
{
|
||||
/// <summary>
|
||||
/// Supported country of exercise for performer profile validations.
|
||||
/// </summary>
|
||||
public class Country
|
||||
{
|
||||
[Key]
|
||||
[MaxLength(2)]
|
||||
[MinLength(2)]
|
||||
public string Code { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(64)]
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ namespace Yavsc.Workflow
|
|||
public interface IPerformerProfile
|
||||
{
|
||||
string PerformerId { get; set; }
|
||||
string ExerciseCountryCode { get; set; }
|
||||
string SIREN { get; set; }
|
||||
bool AcceptNotifications { get; set; }
|
||||
long OrganizationAddressId { get; set; }
|
||||
|
|
|
|||
86
src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs
Normal file
86
src/Yavsc.Abstract/Workflow/PerformerCodeInputValidation.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Yavsc.Models.Workflow
|
||||
{
|
||||
/// <summary>
|
||||
/// Validation rule for performer business code input by country.
|
||||
/// </summary>
|
||||
public class PerformerCodeInputValidation
|
||||
{
|
||||
[Key]
|
||||
public long Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(2)]
|
||||
[MinLength(2)]
|
||||
public string CountryCode { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(256)]
|
||||
public string RegularExpression { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(128)]
|
||||
public string ErrorMessage { get; set; } = string.Empty;
|
||||
|
||||
[ForeignKey(nameof(CountryCode))]
|
||||
public Country Country { get; set; }
|
||||
}
|
||||
|
||||
public static class PerformerCodeInputValidationCatalog
|
||||
{
|
||||
public static readonly IReadOnlyList<Country> Countries = new List<Country>
|
||||
{
|
||||
new() { Code = "fr", DisplayName = "France" },
|
||||
new() { Code = "en", DisplayName = "England" },
|
||||
new() { Code = "pt", DisplayName = "Portugal" },
|
||||
};
|
||||
|
||||
public static readonly IReadOnlyList<PerformerCodeInputValidation> Rules = new List<PerformerCodeInputValidation>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1,
|
||||
CountryCode = "fr",
|
||||
RegularExpression = "^[0-9]{9,14}$",
|
||||
ErrorMessage = "Le code FR doit contenir entre 9 et 14 chiffres."
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 2,
|
||||
CountryCode = "en",
|
||||
RegularExpression = "^[A-Za-z0-9]{8,14}$",
|
||||
ErrorMessage = "Le code EN doit contenir entre 8 et 14 caracteres alphanumeriques."
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 3,
|
||||
CountryCode = "pt",
|
||||
RegularExpression = "^[0-9]{9}$",
|
||||
ErrorMessage = "Le code PT doit contenir exactement 9 chiffres."
|
||||
},
|
||||
};
|
||||
|
||||
public static string NormalizeCountryCode(string? countryCode)
|
||||
{
|
||||
return (countryCode ?? string.Empty).Trim().ToLowerInvariant();
|
||||
}
|
||||
|
||||
public static PerformerCodeInputValidation? GetRule(string? countryCode)
|
||||
{
|
||||
var normalized = NormalizeCountryCode(countryCode);
|
||||
foreach (var rule in Rules)
|
||||
{
|
||||
if (string.Equals(rule.CountryCode, normalized, StringComparison.Ordinal))
|
||||
{
|
||||
return rule;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using Yavsc.Models.Relationship;
|
||||
using Yavsc.Models.Workflow;
|
||||
|
||||
namespace Yavsc.Tests.NonRegression;
|
||||
|
||||
public class PerformerCodeInputValidationTests
|
||||
{
|
||||
[Fact]
|
||||
public void Validate_rejects_unknown_country_code()
|
||||
{
|
||||
var profile = CreateBaseProfile();
|
||||
profile.ExerciseCountryCode = "de";
|
||||
profile.SIREN = "123456789";
|
||||
|
||||
var results = Validate(profile);
|
||||
|
||||
Assert.Contains(results, r => r.MemberNames.Contains(nameof(PerformerProfile.ExerciseCountryCode)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_rejects_code_not_matching_country_rule()
|
||||
{
|
||||
var profile = CreateBaseProfile();
|
||||
profile.ExerciseCountryCode = "pt";
|
||||
profile.SIREN = "ABC123";
|
||||
|
||||
var results = Validate(profile);
|
||||
|
||||
Assert.Contains(results, r => r.MemberNames.Contains(nameof(PerformerProfile.SIREN)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_accepts_country_specific_valid_codes()
|
||||
{
|
||||
var fr = CreateBaseProfile();
|
||||
fr.ExerciseCountryCode = "fr";
|
||||
fr.SIREN = "123456789";
|
||||
|
||||
var en = CreateBaseProfile();
|
||||
en.ExerciseCountryCode = "en";
|
||||
en.SIREN = "AB12CD34";
|
||||
|
||||
var pt = CreateBaseProfile();
|
||||
pt.ExerciseCountryCode = "pt";
|
||||
pt.SIREN = "501964843";
|
||||
|
||||
Assert.Empty(Validate(fr));
|
||||
Assert.Empty(Validate(en));
|
||||
Assert.Empty(Validate(pt));
|
||||
}
|
||||
|
||||
private static PerformerProfile CreateBaseProfile()
|
||||
{
|
||||
return new PerformerProfile
|
||||
{
|
||||
PerformerId = "perf-1",
|
||||
SIREN = "123456789",
|
||||
ExerciseCountryCode = "fr",
|
||||
OrganizationAddress = new Location
|
||||
{
|
||||
Address = "1 rue du Test",
|
||||
Latitude = 48.8566,
|
||||
Longitude = 2.3522,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static List<ValidationResult> Validate(PerformerProfile profile)
|
||||
{
|
||||
var ctx = new ValidationContext(profile);
|
||||
var results = new List<ValidationResult>();
|
||||
Validator.TryValidateObject(profile, ctx, results, validateAllProperties: true);
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Yavsc.Models.Workflow;
|
||||
using Yavsc.Helpers;
|
||||
using Yavsc.Models.Relationship;
|
||||
|
|
@ -544,15 +545,19 @@ namespace Yavsc.Controllers
|
|||
{
|
||||
var currentProfile = _dbContext.Performers.Include(x => x.OrganizationAddress)
|
||||
.First(x => x.PerformerId == uid);
|
||||
currentProfile.ExerciseCountryCode = NormalizeCountryCodeOrDefault(currentProfile.ExerciseCountryCode);
|
||||
ViewBag.Activities = _dbContext.ActivityItems(existing.Activity);
|
||||
SetExerciseCountries(currentProfile.ExerciseCountryCode);
|
||||
return View(currentProfile);
|
||||
}
|
||||
|
||||
ViewBag.Activities = _dbContext.ActivityItems(new List<UserActivity>());
|
||||
SetExerciseCountries("fr");
|
||||
return View(new PerformerProfile
|
||||
{
|
||||
PerformerId = user.Id,
|
||||
Performer = user,
|
||||
ExerciseCountryCode = "fr",
|
||||
OrganizationAddress = new Location()
|
||||
});
|
||||
}
|
||||
|
|
@ -563,28 +568,32 @@ namespace Yavsc.Controllers
|
|||
{
|
||||
var user = GetCurrentUserAsync().Result;
|
||||
var uid = user.Id;
|
||||
model.ExerciseCountryCode = NormalizeCountryCodeOrDefault(model.ExerciseCountryCode);
|
||||
try
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
|
||||
var exSiren = await _dbContext.ExceptionsSIREN.FirstOrDefaultAsync(
|
||||
ex => ex.SIREN == model.SIREN
|
||||
);
|
||||
if (exSiren != null)
|
||||
var isFrenchPerformerCode = string.Equals(model.ExerciseCountryCode, "fr", StringComparison.Ordinal);
|
||||
if (isFrenchPerformerCode)
|
||||
{
|
||||
_logger.LogInformation("Exception SIREN:" + exSiren);
|
||||
}
|
||||
else
|
||||
{
|
||||
var taskCheck = await _cchecker.CheckAsync(model.SIREN);
|
||||
if (!taskCheck.success)
|
||||
var exSiren = await _dbContext.ExceptionsSIREN.FirstOrDefaultAsync(
|
||||
ex => ex.SIREN == model.SIREN
|
||||
);
|
||||
if (exSiren != null)
|
||||
{
|
||||
ModelState.AddModelError(
|
||||
"SIREN",
|
||||
_SR["Invalid company number"] + " (" + taskCheck.errorCode + ")"
|
||||
);
|
||||
_logger.LogInformation($"Invalid company number: {model.SIREN}/{taskCheck.errorType}/{taskCheck.errorCode}/{taskCheck.errorMessage}" );
|
||||
_logger.LogInformation("Exception SIREN:" + exSiren);
|
||||
}
|
||||
else
|
||||
{
|
||||
var taskCheck = await _cchecker.CheckAsync(model.SIREN);
|
||||
if (!taskCheck.success)
|
||||
{
|
||||
ModelState.AddModelError(
|
||||
"SIREN",
|
||||
_SR["Invalid company number"] + " (" + taskCheck.errorCode + ")"
|
||||
);
|
||||
_logger.LogInformation($"Invalid company number: {model.SIREN}/{taskCheck.errorType}/{taskCheck.errorCode}/{taskCheck.errorMessage}" );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -622,10 +631,32 @@ namespace Yavsc.Controllers
|
|||
}
|
||||
ViewBag.Activities = _dbContext.ActivityItems(new List<UserActivity>());
|
||||
ViewBag.GoogleSettings = _googleSettings;
|
||||
SetExerciseCountries(model.ExerciseCountryCode);
|
||||
model.Performer = _dbContext.Users.Single(u=>u.Id == model.PerformerId);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private static string NormalizeCountryCodeOrDefault(string? code)
|
||||
{
|
||||
var normalized = PerformerCodeInputValidationCatalog.NormalizeCountryCode(code);
|
||||
if (string.IsNullOrWhiteSpace(normalized))
|
||||
{
|
||||
return "fr";
|
||||
}
|
||||
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private void SetExerciseCountries(string? selectedCountryCode)
|
||||
{
|
||||
var selected = NormalizeCountryCodeOrDefault(selectedCountryCode);
|
||||
ViewBag.ExerciseCountries = new SelectList(
|
||||
PerformerCodeInputValidationCatalog.Countries,
|
||||
nameof(Country.Code),
|
||||
nameof(Country.DisplayName),
|
||||
selected);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> UnsetActivity()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -67,6 +67,15 @@
|
|||
<span asp-validation-for="Active" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
<label asp-for="ExerciseCountryCode" class="col-md-2 control-label">Pays d'exercice</label>
|
||||
<div class="col-md-10">
|
||||
<select asp-for="ExerciseCountryCode" asp-items="ViewBag.ExerciseCountries" class="form-control"></select>
|
||||
|
||||
<span asp-validation-for="ExerciseCountryCode" class="text-danger"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
|
||||
<label asp-for="SIREN" class="col-md-2 control-label">SIREN</label>
|
||||
|
|
|
|||
|
|
@ -109,6 +109,21 @@ namespace Yavsc.Models
|
|||
|
||||
builder.Entity<Activity>().Property(a => a.ParentCode).IsRequired(false);
|
||||
|
||||
builder.Entity<Country>().HasKey(c => c.Code);
|
||||
builder.Entity<PerformerCodeInputValidation>()
|
||||
.HasOne(v => v.Country)
|
||||
.WithMany()
|
||||
.HasForeignKey(v => v.CountryCode)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.Entity<Country>().HasData(
|
||||
PerformerCodeInputValidationCatalog.Countries
|
||||
);
|
||||
|
||||
builder.Entity<PerformerCodeInputValidation>().HasData(
|
||||
PerformerCodeInputValidationCatalog.Rules
|
||||
);
|
||||
|
||||
builder.Entity<Client>().Property("Id").UseIdentityAlwaysColumn();
|
||||
builder.Entity<ClientSecret>().Property("Id").UseIdentityAlwaysColumn();
|
||||
builder.Entity<ClientScope>().Property("Id").UseIdentityAlwaysColumn();
|
||||
|
|
@ -262,6 +277,8 @@ namespace Yavsc.Models
|
|||
|
||||
public DbSet<HairMultiCutQuery> HairMultiCutQueries { get; set; }
|
||||
public DbSet<PerformerProfile> Performers { get; set; }
|
||||
public DbSet<Country> Countries { get; set; }
|
||||
public DbSet<PerformerCodeInputValidation> PerformerCodeInputValidations { get; set; }
|
||||
|
||||
public DbSet<Estimate> Estimates { get; set; }
|
||||
public DbSet<Signature> Signatures { get; set; }
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ using System.ComponentModel.DataAnnotations.Schema;
|
|||
namespace Yavsc.Models.Workflow
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using Models.Relationship;
|
||||
using Newtonsoft.Json;
|
||||
using Yavsc.Attributes.Validation;
|
||||
using Yavsc.Workflow;
|
||||
|
||||
public class PerformerProfile : IPerformerProfile {
|
||||
public class PerformerProfile : IPerformerProfile, IValidatableObject {
|
||||
|
||||
[Key]
|
||||
public string PerformerId { get; set; }
|
||||
|
|
@ -20,8 +22,11 @@ namespace Yavsc.Models.Workflow
|
|||
[Display(Name="Activity"), JsonIgnore]
|
||||
public virtual List<UserActivity> Activity { get; set; }
|
||||
|
||||
[Required,YaStringLength(14),Display(Name="SIREN"),
|
||||
RegularExpression(@"^[0-9]{9,14}$", ErrorMessage = "Only numbers are allowed here")]
|
||||
[Required, Display(Name = "Country of exercise")]
|
||||
[MinLength(2), MaxLength(2)]
|
||||
public string ExerciseCountryCode { get; set; } = "fr";
|
||||
|
||||
[Required,YaStringLength(14),Display(Name="SIREN")]
|
||||
public string SIREN { get; set; }
|
||||
|
||||
public long OrganizationAddressId { get; set; }
|
||||
|
|
@ -60,5 +65,40 @@ namespace Yavsc.Models.Workflow
|
|||
return Performer?.Posts?.Count > 0 ;
|
||||
} }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
var countryCode = PerformerCodeInputValidationCatalog.NormalizeCountryCode(ExerciseCountryCode);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(countryCode))
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
"Le pays d'exercice est requis.",
|
||||
new[] { nameof(ExerciseCountryCode) });
|
||||
yield break;
|
||||
}
|
||||
|
||||
var rule = PerformerCodeInputValidationCatalog.GetRule(countryCode);
|
||||
if (rule is null)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
"Le pays d'exercice doit etre l'un des suivants: fr, en, pt.",
|
||||
new[] { nameof(ExerciseCountryCode) });
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(SIREN))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var normalizedCode = SIREN.Trim();
|
||||
if (!Regex.IsMatch(normalizedCode, rule.RegularExpression, RegexOptions.CultureInvariant))
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
rule.ErrorMessage,
|
||||
new[] { nameof(SIREN) });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue