got saved a RdvQuery

This commit is contained in:
Paul Schneider 2026-09-13 03:09:12 +01:00
commit cbb59f019d
Signed by: notazof
GPG key ID: 1DD5D838E5343B06
17 changed files with 616 additions and 288 deletions

View file

@ -7,6 +7,8 @@ using Yavsc.Billing;
using Yavsc.Helpers;
using Yavsc.ViewModels;
using Yavsc.Models.Billing;
using Yavsc.Models.Haircut;
using Yavsc.Models.Workflow;
using Yavsc.Server.Models.FileSystem;
namespace Yavsc.ApiControllers
@ -121,12 +123,38 @@ namespace Yavsc.ApiControllers
WorkflowHelpers.ConfigureBillingService();
}
var commands = dbContext.Set<NominativeServiceCommand>()
// Query known derived types explicitly so legacy rows with
// invalid/empty discriminator values are naturally ignored.
var rdvCommands = dbContext.Set<RdvQuery>()
.AsNoTracking()
.Where(q => q.PerformerId == uid)
.Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.InProgress)
.Cast<NominativeServiceCommand>()
.ToList();
var hairCommands = dbContext.Set<HairCutQuery>()
.AsNoTracking()
.Where(q => q.PerformerId == uid)
.Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.InProgress)
.Cast<NominativeServiceCommand>()
.ToList();
var hairMultiCommands = dbContext.Set<HairMultiCutQuery>()
.AsNoTracking()
.Where(q => q.PerformerId == uid)
.Where(q => q.Status == QueryStatus.Inserted
|| q.Status == QueryStatus.Accepted
|| q.Status == QueryStatus.InProgress)
.Cast<NominativeServiceCommand>()
.ToList();
var commands = rdvCommands
.Concat(hairCommands)
.Concat(hairMultiCommands)
.OrderByDescending(q => q.DateModified)
.ThenByDescending(q => q.Id)
.ToList();

View file

@ -1,193 +0,0 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Yavsc.Controllers
{
using System;
using Yavsc.Models;
using Yavsc.Models.Workflow;
using Yavsc.Models.Billing;
using Yavsc.Abstract.Identity;
using Microsoft.EntityFrameworkCore;
using Yavsc.Server.Helpers;
[Authorize]
[Produces("application/json")]
[Route(Constants.APIPrefix + "/bookquery"), Authorize("Performer")]
public class BookQueryApiController : Controller
{
private ApplicationDbContext _context;
private ILogger _logger;
public BookQueryApiController(ApplicationDbContext context, ILoggerFactory loggerFactory)
{
_context = context;
_logger = loggerFactory.CreateLogger<BookQueryApiController>();
}
// GET: api/BookQueryApi
/// <summary>
/// Book queries, by creation order
/// </summary>
/// <param name="maxId">returned Ids must be lower than this value</param>
/// <returns>book queries</returns>
[HttpGet]
public IEnumerable<RdvQueryProviderInfo> GetCommands(long maxId=long.MaxValue)
{
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
var now = DateTime.UtcNow;
var result = _context.RdvQueries.Include(c => c.Location).
Include(c => c.Client).Where(c => c.PerformerId == uid && c.Id < maxId && c.EventDate > now
&& c.ValidationDate == null).
Select(c => new RdvQueryProviderInfo
{
Client = new ClientProviderInfo {
UserName = c.Client.UserName,
UserId = c.ClientId,
Avatar = c.Client.Avatar },
Location = c.Location,
EventDate = c.EventDate,
Id = c.Id,
Previsional = c.Provisional,
Reason = c.Reason,
ActivityCode = c.ActivityCode,
BillingCode = BillingCodes.Rdv
}).
OrderBy(c=>c.Id).
Take(25);
return result;
}
// GET: api/BookQueryApi/5
[HttpGet("{id}", Name = "GetBookQuery")]
public IActionResult GetBookQuery([FromRoute] long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
RdvQuery bookQuery = _context.RdvQueries.Where(c => c.ClientId == uid || c.PerformerId == uid).Single(m => m.Id == id);
if (bookQuery == null)
{
return NotFound();
}
return Ok(bookQuery);
}
// PUT: api/BookQueryApi/5
[HttpPut("{id}")]
public IActionResult PutBookQuery(long id, [FromBody] RdvQuery bookQuery)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id != bookQuery.Id)
{
return BadRequest();
}
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (bookQuery.ClientId != uid)
return NotFound();
_context.Entry(bookQuery).State = EntityState.Modified;
try
{
_context.SaveChanges(User.GetUserId());
}
catch (DbUpdateConcurrencyException)
{
if (!BookQueryExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return new StatusCodeResult(StatusCodes.Status204NoContent);
}
// POST: api/BookQueryApi
[HttpPost]
public IActionResult PostBookQuery([FromBody] RdvQuery bookQuery)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (bookQuery.ClientId != uid)
{
ModelState.AddModelError("ClientId", "You must be the client at creating a book query");
return new BadRequestObjectResult(ModelState);
}
_context.RdvQueries.Add(bookQuery);
try
{
_context.SaveChanges(User.GetUserId());
}
catch (DbUpdateException)
{
if (BookQueryExists(bookQuery.Id))
{
return new StatusCodeResult(StatusCodes.Status409Conflict);
}
else
{
throw;
}
}
return CreatedAtRoute("GetBookQuery", new { id = bookQuery.Id }, bookQuery);
}
// DELETE: api/BookQueryApi/5
[HttpDelete("{id}")]
public IActionResult DeleteBookQuery(long id)
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var uid = User.FindFirstValue(ClaimTypes.NameIdentifier);
RdvQuery bookQuery = _context.RdvQueries.Single(m => m.Id == id);
if (bookQuery == null)
{
return NotFound();
}
if (bookQuery.ClientId != uid) return NotFound();
_context.RdvQueries.Remove(bookQuery);
_context.SaveChanges(User.GetUserId());
return Ok(bookQuery);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
_context.Dispose();
}
base.Dispose(disposing);
}
private bool BookQueryExists(long id)
{
return _context.RdvQueries.Count(e => e.Id == id) > 0;
}
}
}

View file

@ -1,8 +1,13 @@
#nullable enable annotations
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Npgsql;
using Yavsc.Models;
using Yavsc.Models.Billing;
using Yavsc.Models.Relationship;
using Yavsc.Models.Workflow;
using Yavsc.Server.Helpers;
@ -83,30 +88,32 @@ public class RdvQueryApiController : Controller
return BadRequest(ModelState);
}
if (query.Location is not null)
if (query.Location is null)
{
var existingLocation = await _context.Locations.FirstOrDefaultAsync(
x => x.Address == query.Location.Address
&& x.Longitude == query.Location.Longitude
&& x.Latitude == query.Location.Latitude,
cancellationToken);
if (existingLocation is not null)
{
query.Location = existingLocation;
}
else
{
_context.Attach(query.Location);
}
return BadRequest(new { Error = "location is required" });
}
_context.RdvQueries.Add(query);
var resolvedLocation = await ResolveLocationAsync(query.Location, cancellationToken);
if (resolvedLocation is null)
{
return BadRequest(new { Error = "location payload is invalid" });
}
await PersistLocationIfNeededAsync(resolvedLocation, uid, cancellationToken);
query.Location = resolvedLocation;
var addedEntry = _context.RdvQueries.Add(query);
EnsureLocationForeignKey(addedEntry, resolvedLocation.Id);
try
{
await _context.SaveChangesAsync(User.GetUserId(), cancellationToken);
}
catch (DbUpdateException ex) when (IsLocationForeignKeyViolation(ex))
{
return BadRequest(new { Error = "location reference is invalid" });
}
catch (DbUpdateException)
{
if (QueryExists(query.Id))
@ -149,17 +156,16 @@ public class RdvQueryApiController : Controller
if (query.Location is not null)
{
var resolvedLocation = await _context.Locations.FirstOrDefaultAsync(
x => x.Address == query.Location.Address
&& x.Longitude == query.Location.Longitude
&& x.Latitude == query.Location.Latitude,
cancellationToken);
existing.Location = resolvedLocation ?? query.Location;
var resolvedLocation = await ResolveLocationAsync(query.Location, cancellationToken);
if (resolvedLocation is null)
{
_context.Attach(query.Location);
return BadRequest(new { Error = "location payload is invalid" });
}
await PersistLocationIfNeededAsync(resolvedLocation, uid, cancellationToken);
existing.Location = resolvedLocation;
EnsureLocationForeignKey(_context.Entry(existing), resolvedLocation.Id);
}
try
@ -175,6 +181,10 @@ public class RdvQueryApiController : Controller
throw;
}
catch (DbUpdateException ex) when (IsLocationForeignKeyViolation(ex))
{
return BadRequest(new { Error = "location reference is invalid" });
}
return NoContent();
}
@ -208,6 +218,78 @@ public class RdvQueryApiController : Controller
return _context.RdvQueries.Any(e => e.Id == id);
}
private async Task<Location?> ResolveLocationAsync(Location postedLocation, CancellationToken cancellationToken)
{
if (postedLocation.Id > 0)
{
var byId = await _context.Locations
.FirstOrDefaultAsync(x => x.Id == postedLocation.Id, cancellationToken);
if (byId is not null)
{
return byId;
}
}
if (string.IsNullOrWhiteSpace(postedLocation.Address))
{
return null;
}
var existingByCoordinates = await _context.Locations.FirstOrDefaultAsync(
x => x.Address == postedLocation.Address
&& x.Longitude == postedLocation.Longitude
&& x.Latitude == postedLocation.Latitude,
cancellationToken);
if (existingByCoordinates is not null)
{
return existingByCoordinates;
}
// Treat unknown location ids as client-side placeholders and insert a new row.
postedLocation.Id = 0;
_context.Locations.Add(postedLocation);
return postedLocation;
}
private async Task PersistLocationIfNeededAsync(Location location, string userId, CancellationToken cancellationToken)
{
if (_context.Entry(location).State != EntityState.Added)
{
return;
}
await _context.SaveChangesAsync(userId, cancellationToken);
}
private static bool IsLocationForeignKeyViolation(DbUpdateException ex)
{
if (ex.InnerException is not PostgresException pg)
{
return false;
}
return pg.SqlState == PostgresErrorCodes.ForeignKeyViolation
&& string.Equals(pg.ConstraintName, "FK_NominativeServiceCommand_Locations_LocationId", StringComparison.Ordinal);
}
private static void EnsureLocationForeignKey(EntityEntry<RdvQuery> entry, long locationId)
{
SetFkIfPresent(entry, "LocationId", locationId);
SetFkIfPresent(entry, "RdvQuery_LocationId", locationId);
}
private static void SetFkIfPresent(EntityEntry<RdvQuery> entry, string propertyName, long value)
{
var property = entry.Metadata.FindProperty(propertyName);
if (property is null)
{
return;
}
entry.Property(propertyName).CurrentValue = value;
}
private static DateTime EnsureUtc(DateTime value)
{
return value.Kind switch