Sweep kiosk intake submission for FK/null bugs
- Fix Jobs.Id FK violation: save job first with CompleteAsync() to get its DB-assigned Id, THEN set session.LinkedJobId and save again. Previously job.Id was still 0 when written to the nullable FK column. - Replace ?? 1 fallbacks on JobStatusId/JobPriorityId with explicit InvalidOperationException — hardcoded 1 may not exist in the company's lookup tables; now fails loud with a clear message instead of an FK error. - Add ValidateSessionState check to Terms POST so expired/already-submitted sessions don't re-run ProcessSubmissionAsync and create duplicate jobs. - Null-guard session.JobDescription before slicing for notification snippet. - Tighten catch block: wrap the fallback CompleteAsync in its own try/catch so a secondary failure doesn't mask the original error in logs. - Swap Job.Description / SpecialInstructions: Description now holds the actual job description text; SpecialInstructions records the intake source. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -394,6 +394,9 @@ public class KioskController : Controller
|
||||
var session = await LoadSessionAsync(token);
|
||||
if (session == null) return View("KioskError", "Session not found.");
|
||||
|
||||
// Expired/already-submitted sessions go straight to Confirmation
|
||||
if (!await ValidateSessionState(session)) return RedirectToAction(nameof(Confirmation), new { token });
|
||||
|
||||
// Require signature for in-person sessions
|
||||
if (session.SessionType == KioskSessionType.InPerson &&
|
||||
string.IsNullOrEmpty(dto.SignatureDataBase64))
|
||||
@@ -423,8 +426,9 @@ public class KioskController : Controller
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing kiosk submission for session {SessionToken}", token);
|
||||
// Don't fail the customer-facing page — save what we have and let staff convert manually
|
||||
await _unitOfWork.CompleteAsync();
|
||||
// Customer-facing page always succeeds — staff can convert the session manually.
|
||||
// Persist the session's agreed/submitted state even if job creation failed.
|
||||
try { await _unitOfWork.CompleteAsync(); } catch { /* best-effort */ }
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Confirmation), new { token });
|
||||
@@ -589,9 +593,14 @@ public class KioskController : Controller
|
||||
// 3. Create Job in Pending status with Normal priority
|
||||
var statuses = await _lookupCache.GetJobStatusLookupsAsync(companyId);
|
||||
var pendingStatus = statuses.FirstOrDefault(s => s.StatusCode == AppConstants.StatusCodes.Job.Pending);
|
||||
if (pendingStatus == null)
|
||||
throw new InvalidOperationException($"No Pending job status found for company {companyId}. Run Seed Data from Platform Management.");
|
||||
|
||||
var priorities = await _lookupCache.GetJobPriorityLookupsAsync(companyId);
|
||||
var normalPriority = priorities.FirstOrDefault(p => p.PriorityCode == "NORMAL") ?? priorities.FirstOrDefault();
|
||||
var normalPriority = priorities.FirstOrDefault(p => p.PriorityCode == "NORMAL")
|
||||
?? priorities.FirstOrDefault();
|
||||
if (normalPriority == null)
|
||||
throw new InvalidOperationException($"No job priority rows found for company {companyId}. Run Seed Data from Platform Management.");
|
||||
|
||||
var jobNumber = await GenerateJobNumberAsync(companyId);
|
||||
var job = new Job
|
||||
@@ -599,29 +608,27 @@ public class KioskController : Controller
|
||||
CompanyId = companyId,
|
||||
CustomerId = customer!.Id,
|
||||
JobNumber = jobNumber,
|
||||
JobStatusId = pendingStatus?.Id ?? 1,
|
||||
JobPriorityId = normalPriority?.Id ?? 1,
|
||||
SpecialInstructions = session.JobDescription,
|
||||
Description = $"Walk-in intake — {session.CustomerFirstName} {session.CustomerLastName}".Trim()
|
||||
JobStatusId = pendingStatus.Id,
|
||||
JobPriorityId = normalPriority.Id,
|
||||
Description = session.JobDescription,
|
||||
SpecialInstructions = $"Source: {session.SessionType} kiosk intake"
|
||||
};
|
||||
|
||||
await _unitOfWork.Jobs.AddAsync(job);
|
||||
|
||||
// 4. Update session links
|
||||
session.LinkedCustomerId = customer.Id;
|
||||
session.LinkedJobId = job.Id; // will be populated after SaveChanges below
|
||||
// Save the job first so EF generates its Id, then link the session.
|
||||
// Setting session.LinkedJobId = job.Id before CompleteAsync would write 0
|
||||
// to the FK column because the DB hasn't assigned the Id yet.
|
||||
await _unitOfWork.CompleteAsync(); // job.Id is now valid
|
||||
|
||||
// 4. Update session links now that both Ids exist
|
||||
session.LinkedCustomerId = customer.Id;
|
||||
session.LinkedJobId = job.Id;
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
// job.Id is now set — update session again if needed
|
||||
if (session.LinkedJobId == 0)
|
||||
{
|
||||
session.LinkedJobId = job.Id;
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
|
||||
// 5. Fire staff notification
|
||||
var snippet = session.JobDescription.Length > 60 ? session.JobDescription[..60] + "…" : session.JobDescription;
|
||||
var jobDesc = session.JobDescription ?? "";
|
||||
var snippet = jobDesc.Length > 60 ? jobDesc[..60] + "…" : jobDesc;
|
||||
var fullName = $"{session.CustomerFirstName} {session.CustomerLastName}".Trim();
|
||||
await _inApp.CreateAsync(
|
||||
companyId,
|
||||
|
||||
Reference in New Issue
Block a user