Compare commits

...

8 Commits

Author SHA1 Message Date
Hargata Softworks
2ab822af8f
Merge pull request #1126 from hargata/Hargata/fix.reminder.bug
fix UI bug when creating reminder record from other records.
2025-11-05 12:37:32 -07:00
DESKTOP-T0O5CDB\DESK-555BD
0cef3c9044 fix UI bug when creating reminder record from other records. 2025-11-05 12:36:33 -07:00
Hargata Softworks
9ef2124c8a
Merge pull request #1125 from hargata/Hargata/1119
add GET API Endpoint to return all extrafields
2025-11-05 11:06:17 -07:00
DESKTOP-T0O5CDB\DESK-555BD
fd9d57c3db add documentation 2025-11-05 11:04:35 -07:00
DESKTOP-T0O5CDB\DESK-555BD
a3e0da1ebb add GET API Endpoint to return all extrafields 2025-11-05 10:33:48 -07:00
Hargata Softworks
ebca12d82a
Merge pull request #1124 from hargata/Hargata/add.api
API Enhancements
2025-11-05 09:11:21 -07:00
DESKTOP-T0O5CDB\DESK-555BD
9b00933aa8 fix action item progress 2025-11-05 09:10:41 -07:00
DESKTOP-T0O5CDB\DESK-555BD
98ef7d455b Add check to ensure that reminder for inspection still exists. Add recordId to response object returned from API 2025-11-05 07:52:43 -07:00
12 changed files with 173 additions and 20 deletions

View File

@ -28,6 +28,7 @@ namespace CarCareTracker.Controllers
private readonly IInspectionRecordTemplateDataAccess _inspectionRecordTemplateDataAccess; private readonly IInspectionRecordTemplateDataAccess _inspectionRecordTemplateDataAccess;
private readonly IUserAccessDataAccess _userAccessDataAccess; private readonly IUserAccessDataAccess _userAccessDataAccess;
private readonly IUserRecordDataAccess _userRecordDataAccess; private readonly IUserRecordDataAccess _userRecordDataAccess;
private readonly IExtraFieldDataAccess _extraFieldDataAccess;
private readonly IReminderHelper _reminderHelper; private readonly IReminderHelper _reminderHelper;
private readonly IGasHelper _gasHelper; private readonly IGasHelper _gasHelper;
private readonly IUserLogic _userLogic; private readonly IUserLogic _userLogic;
@ -55,6 +56,7 @@ namespace CarCareTracker.Controllers
IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess, IInspectionRecordTemplateDataAccess inspectionRecordTemplateDataAccess,
IUserAccessDataAccess userAccessDataAccess, IUserAccessDataAccess userAccessDataAccess,
IUserRecordDataAccess userRecordDataAccess, IUserRecordDataAccess userRecordDataAccess,
IExtraFieldDataAccess extraFieldDataAccess,
IMailHelper mailHelper, IMailHelper mailHelper,
IFileHelper fileHelper, IFileHelper fileHelper,
IConfigHelper config, IConfigHelper config,
@ -79,6 +81,7 @@ namespace CarCareTracker.Controllers
_inspectionRecordTemplateDataAccess = inspectionRecordTemplateDataAccess; _inspectionRecordTemplateDataAccess = inspectionRecordTemplateDataAccess;
_userAccessDataAccess = userAccessDataAccess; _userAccessDataAccess = userAccessDataAccess;
_userRecordDataAccess = userRecordDataAccess; _userRecordDataAccess = userRecordDataAccess;
_extraFieldDataAccess = extraFieldDataAccess;
_mailHelper = mailHelper; _mailHelper = mailHelper;
_gasHelper = gasHelper; _gasHelper = gasHelper;
_reminderHelper = reminderHelper; _reminderHelper = reminderHelper;
@ -329,7 +332,7 @@ namespace CarCareTracker.Controllers
}; };
_planRecordDataAccess.SavePlanRecordToVehicle(planRecord); _planRecordDataAccess.SavePlanRecordToVehicle(planRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(planRecord, "planrecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromPlanRecord(planRecord, "planrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Plan Record Added")); return Json(OperationResponse.Succeed("Plan Record Added", new { recordId = planRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -545,7 +548,7 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord); _odometerLogic.AutoInsertOdometerRecord(odometerRecord);
} }
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord, "servicerecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(serviceRecord, "servicerecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Service Record Added")); return Json(OperationResponse.Succeed("Service Record Added", new { recordId = serviceRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -742,7 +745,7 @@ namespace CarCareTracker.Controllers
} }
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(repairRecord, "repairrecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(repairRecord, "repairrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Repair Record Added")); return Json(OperationResponse.Succeed("Repair Record Added", new { recordId = repairRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -939,7 +942,7 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord); _odometerLogic.AutoInsertOdometerRecord(odometerRecord);
} }
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord, "upgraderecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGenericRecord(upgradeRecord, "upgraderecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Upgrade Record Added")); return Json(OperationResponse.Succeed("Upgrade Record Added", new { recordId = upgradeRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1158,7 +1161,7 @@ namespace CarCareTracker.Controllers
_taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord); _taxRecordDataAccess.SaveTaxRecordToVehicle(taxRecord);
_vehicleLogic.UpdateRecurringTaxes(vehicleId); _vehicleLogic.UpdateRecurringTaxes(vehicleId);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord, "taxrecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromTaxRecord(taxRecord, "taxrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Tax Record Added")); return Json(OperationResponse.Succeed("Tax Record Added", new { recordId = taxRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1352,7 +1355,7 @@ namespace CarCareTracker.Controllers
}; };
_odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord); _odometerRecordDataAccess.SaveOdometerRecordToVehicle(odometerRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord, "odometerrecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromOdometerRecord(odometerRecord, "odometerrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Odometer Record Added")); return Json(OperationResponse.Succeed("Odometer Record Added", new { recordId = odometerRecord.Id }));
} catch (Exception ex) } catch (Exception ex)
{ {
Response.StatusCode = 500; Response.StatusCode = 500;
@ -1561,7 +1564,7 @@ namespace CarCareTracker.Controllers
_odometerLogic.AutoInsertOdometerRecord(odometerRecord); _odometerLogic.AutoInsertOdometerRecord(odometerRecord);
} }
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord, "gasrecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromGasRecord(gasRecord, "gasrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Gas Record Added")); return Json(OperationResponse.Succeed("Gas Record Added", new { recordId = gasRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1759,7 +1762,7 @@ namespace CarCareTracker.Controllers
}; };
_reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord); _reminderRecordDataAccess.SaveReminderRecordToVehicle(reminderRecord);
StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord, "reminderrecord.add.api", User.Identity.Name)); StaticHelper.NotifyAsync(_config.GetWebHookUrl(), WebHookPayload.FromReminderRecord(reminderRecord, "reminderrecord.add.api", User.Identity.Name));
return Json(OperationResponse.Succeed("Reminder Record Added")); return Json(OperationResponse.Succeed("Reminder Record Added", new { recordId = reminderRecord.Id }));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1980,6 +1983,42 @@ namespace CarCareTracker.Controllers
return Json(OperationResponse.Succeed($"Emails Sent({operationResponses.Count(x => x.Success)}), Emails Failed({operationResponses.Count(x => !x.Success)}), Check Recipient Settings")); return Json(OperationResponse.Succeed($"Emails Sent({operationResponses.Count(x => x.Success)}), Emails Failed({operationResponses.Count(x => !x.Success)}), Check Recipient Settings"));
} }
} }
[HttpGet]
[Route("/api/extrafields")]
public IActionResult GetExtraFields()
{
try
{
List<RecordExtraFieldExportModel> result = new List<RecordExtraFieldExportModel>();
var extraFields = _extraFieldDataAccess.GetExtraFields();
if (extraFields.Any())
{
foreach(RecordExtraField extraField in extraFields)
{
if (extraField.ExtraFields.Any())
{
result.Add(new RecordExtraFieldExportModel
{
RecordType = ((ImportMode)extraField.Id).ToString(),
ExtraFields = extraField.ExtraFields.Select(x => new ExtraFieldExportModel { Name = x.Name, IsRequired = x.IsRequired.ToString(), FieldType = x.FieldType.ToString() }).ToList()
});
}
}
}
if (_config.GetInvariantApi() || Request.Headers.ContainsKey("culture-invariant"))
{
return Json(result, StaticHelper.GetInvariantOption());
}
else
{
return Json(result);
}
} catch (Exception ex)
{
Response.StatusCode = 500;
return Json(OperationResponse.Failed(ex.Message));
}
}
[Authorize(Roles = nameof(UserData.IsRootUser))] [Authorize(Roles = nameof(UserData.IsRootUser))]
[HttpGet] [HttpGet]
[Route("/api/makebackup")] [Route("/api/makebackup")]

View File

@ -38,13 +38,32 @@ namespace CarCareTracker.Controllers
[HttpGet] [HttpGet]
public IActionResult GetEditInspectionRecordTemplatePartialView(int inspectionRecordTemplateId) public IActionResult GetEditInspectionRecordTemplatePartialView(int inspectionRecordTemplateId)
{ {
var result = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId); var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check. //security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId)) if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{ {
return Redirect("/Error/Unauthorized"); return Redirect("/Error/Unauthorized");
} }
return PartialView("Inspection/_InspectionRecordTemplateEditModal", result); if (existingRecord.ReminderRecordId.Any())
{
bool reminderMissing = false;
//check if reminder still exists and is still recurring.
foreach (int reminderRecordId in existingRecord.ReminderRecordId)
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
reminderMissing = true;
break;
}
}
if (reminderMissing)
{
//clear out reminders attached to record.
existingRecord.ReminderRecordId = new List<int>();
}
}
return PartialView("Inspection/_InspectionRecordTemplateEditModal", existingRecord);
} }
[HttpGet] [HttpGet]
public IActionResult GetAddInspectionRecordFieldPartialView() public IActionResult GetAddInspectionRecordFieldPartialView()
@ -111,15 +130,34 @@ namespace CarCareTracker.Controllers
[HttpGet] [HttpGet]
public IActionResult GetAddInspectionRecordPartialView(int inspectionRecordTemplateId) public IActionResult GetAddInspectionRecordPartialView(int inspectionRecordTemplateId)
{ {
var result = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId); var existingRecord = _inspectionRecordTemplateDataAccess.GetInspectionRecordTemplateById(inspectionRecordTemplateId);
//security check. //security check.
if (!_userLogic.UserCanEditVehicle(GetUserID(), result.VehicleId)) if (!_userLogic.UserCanEditVehicle(GetUserID(), existingRecord.VehicleId))
{ {
return Redirect("/Error/Unauthorized"); return Redirect("/Error/Unauthorized");
} }
//populate date //populate date
result.Date = DateTime.Now.ToShortDateString(); existingRecord.Date = DateTime.Now.ToShortDateString();
return PartialView("Inspection/_InspectionRecordModal", result); if (existingRecord.ReminderRecordId.Any())
{
bool reminderMissing = false;
//check if reminder still exists and is still recurring.
foreach (int reminderRecordId in existingRecord.ReminderRecordId)
{
var existingReminder = _reminderRecordDataAccess.GetReminderRecordById(reminderRecordId);
if (existingReminder is null || existingReminder.Id == default || !existingReminder.IsRecurring)
{
reminderMissing = true;
break;
}
}
if (reminderMissing)
{
//clear out reminders attached to record.
existingRecord.ReminderRecordId = new List<int>();
}
}
return PartialView("Inspection/_InspectionRecordModal", existingRecord);
} }
[HttpGet] [HttpGet]
public IActionResult GetViewInspectionRecordPartialView(int inspectionRecordId) public IActionResult GetViewInspectionRecordPartialView(int inspectionRecordId)
@ -200,6 +238,7 @@ namespace CarCareTracker.Controllers
Description = inspectionField.ActionItemDescription, Description = inspectionField.ActionItemDescription,
ImportMode = inspectionField.ActionItemType, ImportMode = inspectionField.ActionItemType,
Priority = inspectionField.ActionItemPriority, Priority = inspectionField.ActionItemPriority,
Progress = PlanProgress.Backlog,
Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}", Notes = $"Auto Insert From Inspection Record: {inspectionRecord.Description}",
Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.InspectionRecord, convertedRecord.Id, convertedRecord.Description) Files = StaticHelper.CreateAttachmentFromRecord(ImportMode.InspectionRecord, convertedRecord.Id, convertedRecord.Description)
}); });

View File

@ -12,6 +12,12 @@ namespace CarCareTracker.External.Implementations
{ {
_liteDB = liteDB; _liteDB = liteDB;
} }
public List<RecordExtraField> GetExtraFields()
{
var db = _liteDB.GetLiteDB();
var table = db.GetCollection<RecordExtraField>(tableName);
return table.FindAll().ToList();
}
public RecordExtraField GetExtraFieldsById(int importMode) public RecordExtraField GetExtraFieldsById(int importMode)
{ {
var db = _liteDB.GetLiteDB(); var db = _liteDB.GetLiteDB();

View File

@ -29,6 +29,29 @@ namespace CarCareTracker.External.Implementations
_logger.LogError(ex.Message); _logger.LogError(ex.Message);
} }
} }
public List<RecordExtraField> GetExtraFields()
{
try
{
string cmd = $"SELECT data FROM app.{tableName}";
var results = new List<RecordExtraField>();
using (var ctext = pgDataSource.CreateCommand(cmd))
{
using (NpgsqlDataReader reader = ctext.ExecuteReader())
while (reader.Read())
{
RecordExtraField result = JsonSerializer.Deserialize<RecordExtraField>(reader["data"] as string);
results.Add(result);
}
}
return results;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new List<RecordExtraField>();
}
}
public RecordExtraField GetExtraFieldsById(int importMode) public RecordExtraField GetExtraFieldsById(int importMode)
{ {
try try

View File

@ -4,6 +4,7 @@ namespace CarCareTracker.External.Interfaces
{ {
public interface IExtraFieldDataAccess public interface IExtraFieldDataAccess
{ {
public List<RecordExtraField> GetExtraFields();
public RecordExtraField GetExtraFieldsById(int importMode); public RecordExtraField GetExtraFieldsById(int importMode);
public bool SaveExtraFields(RecordExtraField record); public bool SaveExtraFields(RecordExtraField record);
} }

View File

@ -1,4 +1,5 @@
using CarCareTracker.Helper; using CarCareTracker.Helper;
using System.Text.Json.Serialization;
namespace CarCareTracker.Models namespace CarCareTracker.Models
{ {
@ -6,12 +7,18 @@ namespace CarCareTracker.Models
{ {
public bool Success { get; set; } public bool Success { get; set; }
public string Message { get; set; } public string Message { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public object? AdditionalData { get; set; }
} }
public class OperationResponse: OperationResponseBase public class OperationResponse: OperationResponseBase
{ {
public static OperationResponse Succeed(string message = "") public static OperationResponse Succeed(string message = "")
{ {
return new OperationResponse { Success = true, Message = message }; return new OperationResponse { Success = true, Message = message};
}
public static OperationResponse Succeed(string message, object additionalData)
{
return new OperationResponse { Success = true, Message = message, AdditionalData = additionalData };
} }
public static OperationResponse Failed(string message = "") public static OperationResponse Failed(string message = "")
{ {

View File

@ -42,5 +42,6 @@
Tags = Tags Tags = Tags
}; };
} }
public bool CreatedFromRecord { get; set; } = false;
} }
} }

View File

@ -183,4 +183,16 @@ namespace CarCareTracker.Models
public string Name { get; set; } public string Name { get; set; }
public string Location { get; set; } public string Location { get; set; }
} }
public class RecordExtraFieldExportModel
{
public string RecordType { get; set; }
public List<ExtraFieldExportModel> ExtraFields { get; set; }
}
public class ExtraFieldExportModel
{
public string Name { get; set; }
[JsonConverter(typeof(FromBoolOptional))]
public string IsRequired { get; set; }
public string FieldType { get; set; }
}
} }

View File

@ -762,6 +762,20 @@
No Params No Params
</div> </div>
</div> </div>
<div class="row api-method">
<div class="col-1">
<span class="badge bg-success">GET</span>
</div>
<div class="col-5 copyable testable">
<code>/api/extrafields</code>
</div>
<div class="col-3">
Returns extra fields configured
</div>
<div class="col-3">
No Params
</div>
</div>
<div class="row api-method"> <div class="row api-method">
<div class="col-1"> <div class="col-1">
<span class="badge bg-primary">POST</span> <span class="badge bg-primary">POST</span>
@ -792,7 +806,8 @@
</div> </div>
<div class="col-3"> <div class="col-3">
(must be root user)<br /> (must be root user)<br />
urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional) urgencies[]=[NotUrgent,Urgent,VeryUrgent,PastDue](optional)<br />
tags - tags separated by space(optional)
</div> </div>
</div> </div>
<div class="row api-method"> <div class="row api-method">

View File

@ -144,6 +144,11 @@
var customMonthInterval = @Model.CustomMonthInterval; var customMonthInterval = @Model.CustomMonthInterval;
var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit'); var customMonthIntervalUnit = decodeHTMLEntities('@Model.CustomMonthIntervalUnit');
function getReminderRecordModelData() { function getReminderRecordModelData() {
return { id: @Model.Id, mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'), monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')} return {
id: @Model.Id,
createdFromRecord: @Model.CreatedFromRecord.ToString().ToLower(),
mileageInterval: decodeHTMLEntities('@Model.ReminderMileageInterval.ToString()'),
monthInterval: decodeHTMLEntities('@Model.ReminderMonthInterval.ToString()')
}
} }
</script> </script>

View File

@ -129,8 +129,12 @@ function saveReminderRecordToVehicle(isEdit) {
if (data) { if (data) {
successToast(isEdit ? "Reminder Updated" : "Reminder Added."); successToast(isEdit ? "Reminder Updated" : "Reminder Added.");
hideAddReminderRecordModal(); hideAddReminderRecordModal();
saveScrollPosition(); if (!getReminderRecordModelData().createdFromRecord) {
getVehicleReminders(formValues.vehicleId); saveScrollPosition();
getVehicleReminders(formValues.vehicleId);
} else {
getVehicleHaveImportantReminders(formValues.vehicleId);
}
} else { } else {
errorToast(genericErrorMessage()); errorToast(genericErrorMessage());
} }

View File

@ -228,6 +228,7 @@ function deleteVehicle(vehicleId) {
} }
function showAddReminderModal(reminderModalInput) { function showAddReminderModal(reminderModalInput) {
if (reminderModalInput != undefined) { if (reminderModalInput != undefined) {
reminderModalInput['createdFromRecord'] = true;
$.post('/Vehicle/GetAddReminderRecordPartialView', { reminderModel: reminderModalInput }, function (data) { $.post('/Vehicle/GetAddReminderRecordPartialView', { reminderModel: reminderModalInput }, function (data) {
$("#reminderRecordModalContent").html(data); $("#reminderRecordModalContent").html(data);
initDatePicker($('#reminderDate'), true); initDatePicker($('#reminderDate'), true);