This commit is contained in:
2020-12-08 23:50:54 +00:00
parent a211470b54
commit dd6d8f8b76
11 changed files with 139 additions and 83 deletions

View File

@@ -29,4 +29,5 @@ Here are all the API level error codes that can be returned by the API server:
| 2206 | Validation error - A text property is required to be unique but an existing record with an identical value was found in the database |
| 2207 | Validation error - The start date must be earlier than the end date |
| 2208 | Validation error - Modifying the object (usually a delete) would break the link to other records in the database and operation was disallowed to preserve data integrity |
| 2209 | Validation error - Indicates the attempted property change is invalid because the value is fixed and cannot be changed |
| 2209 | Validation error - Indicates the attempted property change is invalid because the value is fixed and cannot be changed |
| 2210 | Child object error - Indicates the attempted operation resulted in errors in linked child object |

View File

@@ -27,7 +27,8 @@ namespace AyaNova.Biz
VALIDATION_NOT_UNIQUE = 2206,
VALIDATION_STARTDATE_AFTER_ENDDATE = 2207,
VALIDATION_REFERENTIAL_INTEGRITY = 2208,
VALIDATION_NOT_CHANGEABLE = 2209
VALIDATION_NOT_CHANGEABLE = 2209,
CHILD_OBJECT_ERROR = 2210
/*
| 2000 | API closed - Server is running but access to the API has been closed to all users |

View File

@@ -54,6 +54,8 @@ namespace AyaNova.Biz
return "Modifying the object (usually a delete) would break the link to other records in the database and operation was disallowed to preserve data integrity";
case ApiErrorCode.VALIDATION_NOT_CHANGEABLE:
return "the value is fixed and cannot be changed";
case ApiErrorCode.CHILD_OBJECT_ERROR:
return "Errors in child object during operation";

View File

@@ -13,7 +13,7 @@ namespace AyaNova.Biz
public BizObject()
{
}
@@ -27,7 +27,7 @@ namespace AyaNova.Biz
internal long UserId { get; set; }
internal long UserTranslationId { get; set; }
internal AuthorizationRoles CurrentUserRoles { get; set; }
#endregion
@@ -62,6 +62,12 @@ namespace AyaNova.Biz
_errors.Add(new ValidationError() { Code = errorCode, Message = errorMessage, Target = propertyName });
}
// //Add a bunch of errors, generally from a child object failed operastion
// public void AddErrors(List<ValidationError> errors)
// {
// _errors.AddRange(errors);
// }
public string GetErrorsAsString()
{
if (!HasErrors) return string.Empty;

View File

@@ -151,7 +151,8 @@ namespace AyaNova.Biz
try
{
Customer dbObject = await ct.Customer.SingleOrDefaultAsync(z => z.Id == id);
if (dbObject == null){
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
@@ -159,10 +160,38 @@ namespace AyaNova.Biz
if (HasErrors)
return false;
//todo: delete contacts and customer notes
//see workorder* collections and delete for transactional inclusion etc
//DELETE DIRECT CHILD OBJECTS
//(note: the convention is to allow deletion of children created *in* the same UI area so this will delete contacts, customer notes, but not workorders of this customer for example)
//Will need to possibly display refintegrity error from the *linked* object delete attemp, i.e. Contact for here
//so should test that out so that it flows to the UI so user understands they can't delete the Customer due to a Contact having links to other objects
{
var ContactIds = await ct.User.AsNoTracking().Where(z => z.CustomerId == id).Select(z => z.Id).ToListAsync();
if (ContactIds.Count() > 0)
{
UserBiz b = new UserBiz(ct, UserId, UserTranslationId, CurrentUserRoles);
foreach (long ItemId in ContactIds)
if (!await b.DeleteAsync(ItemId, transaction))
{
AddError(ApiErrorCode.VALIDATION_REQUIRED,b.GetErrorsAsString());
return false;
}
}
}
{
var CustomerNoteIds = await ct.CustomerNote.AsNoTracking().Where(z => z.CustomerId == id).Select(z => z.Id).ToListAsync();
if (CustomerNoteIds.Count() > 0)
{
CustomerNoteBiz b = new CustomerNoteBiz(ct, UserId, UserTranslationId, CurrentUserRoles);
foreach (long ItemId in CustomerNoteIds)
if (!await b.DeleteAsync(ItemId, transaction))
{
AddErrors(b.Errors);
return false;
}
}
}
ct.Customer.Remove(dbObject);
await ct.SaveChangesAsync();
@@ -177,6 +206,7 @@ namespace AyaNova.Biz
}
catch
{
//NOTE: no need to rollback the transaction, it will auto-rollback if not committed and it is disposed when it goes out of scope either way
//Just re-throw for now, let exception handler deal, but in future may want to deal with this more here
throw;
@@ -216,7 +246,7 @@ namespace AyaNova.Biz
{
//skip validation if seeding
if (ServerBootConfig.SEEDING) return;
bool isNew = currentObj == null;
//Name required

View File

@@ -109,39 +109,45 @@ namespace AyaNova.Biz
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> DeleteAsync(long id)
internal async Task<bool> DeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null)
{
using (var transaction = await ct.Database.BeginTransactionAsync())
//this may be part of a larger delete operation involving other objects (e.g. Customer delete and remove contacts)
//if so then there will be a parent transaction otherwise we make our own
Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null;
if (parentTransaction == null)
transaction = await ct.Database.BeginTransactionAsync();
try
{
try
CustomerNote dbObject = await ct.CustomerNote.SingleOrDefaultAsync(m => m.Id == id);
if (dbObject == null)
{
CustomerNote dbObject = await ct.CustomerNote.SingleOrDefaultAsync(m => m.Id == id);
if (dbObject == null){
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
if (HasErrors)
return false;
if (HasErrors)
return false;
ct.CustomerNote.Remove(dbObject);
await ct.SaveChangesAsync();
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
if (HasErrors)
return false;
if (HasErrors)
return false;
ct.CustomerNote.Remove(dbObject);
await ct.SaveChangesAsync();
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "CustomerNote", ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, "CustomerNote", ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
//all good do the commit if it's ours
if (parentTransaction == null)
await transaction.CommitAsync();
// await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
}
catch
{
//Just re-throw for now, let exception handler deal, but in future may want to deal with this more here
throw;
}
return true;
}
catch
{
//Just re-throw for now, let exception handler deal, but in future may want to deal with this more here
throw;
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -183,7 +189,7 @@ namespace AyaNova.Biz
var orderedList = from id in batch join z in batchResults on id equals z.Id select z;
foreach (CustomerNote w in orderedList)
{
var jo = JObject.FromObject(w);
var jo = JObject.FromObject(w);
ReportData.Add(jo);
}
}

View File

@@ -442,65 +442,71 @@ namespace AyaNova.Biz
////////////////////////////////////////////////////////////////////////////////////////////////
//DELETE
//
internal async Task<bool> DeleteAsync(long id)
internal async Task<bool> DeleteAsync(long id, Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction parentTransaction = null)
{
using (var transaction = await ct.Database.BeginTransactionAsync())
//this may be part of a larger delete operation involving other objects (e.g. Customer delete and remove contacts)
//if so then there will be a parent transaction otherwise we make our own
Microsoft.EntityFrameworkCore.Storage.IDbContextTransaction transaction = null;
if (parentTransaction == null)
transaction = await ct.Database.BeginTransactionAsync();
try
{
try
User dbObject = await ct.User.SingleOrDefaultAsync(z => z.Id == id);
if (dbObject == null)
{
User dbObject = await ct.User.SingleOrDefaultAsync(z => z.Id == id);
if (dbObject == null)
{
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
AddError(ApiErrorCode.NOT_FOUND);
return false;
}
//Also used for Contacts (customer type user or ho type user)
//by users with no User right but with Customer rights so need to double check here
if (
(dbObject.IsOutsideUser && !Authorized.HasDeleteRole(CurrentUserRoles, AyaType.Customer)) ||
(!dbObject.IsOutsideUser && !Authorized.HasDeleteRole(CurrentUserRoles, AyaType.User))
)
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return false;
}
//Also used for Contacts (customer type user or ho type user)
//by users with no User right but with Customer rights so need to double check here
if (
(dbObject.IsOutsideUser && !Authorized.HasDeleteRole(CurrentUserRoles, AyaType.Customer)) ||
(!dbObject.IsOutsideUser && !Authorized.HasDeleteRole(CurrentUserRoles, AyaType.User))
)
{
AddError(ApiErrorCode.NOT_AUTHORIZED);
return false;
}
await ValidateCanDelete(dbObject);
if (HasErrors)
return false;
await ValidateCanDelete(dbObject);
if (HasErrors)
return false;
//Delete sibling objects
//USEROPTIONS
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from auseroptions where userid = {dbObject.Id}");
//NOTIFY SUBSCRIPTIONS
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from anotifysubscription where userid = {dbObject.Id}");
//personal datalistview
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adatalistview where public = {false} and userid = {dbObject.Id}");
//Dashboard view
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adashboardview where userid = {dbObject.Id}");
//Delete sibling objects
//USEROPTIONS
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from auseroptions where userid = {dbObject.Id}");
//NOTIFY SUBSCRIPTIONS
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from anotifysubscription where userid = {dbObject.Id}");
//personal datalistview
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adatalistview where public = {false} and userid = {dbObject.Id}");
//Dashboard view
await ct.Database.ExecuteSqlInterpolatedAsync($"delete from adashboardview where userid = {dbObject.Id}");
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
//Remove the object
ct.User.Remove(dbObject);
await ct.SaveChangesAsync();
//Remove the object
ct.User.Remove(dbObject);
await ct.SaveChangesAsync();
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);
await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, BizType, ct);
await TagBiz.ProcessDeleteTagsInRepositoryAsync(ct, dbObject.Tags);
await FileUtil.DeleteAttachmentsForObjectAsync(BizType, dbObject.Id, ct);
//all good do the commit if it's ours
if (parentTransaction == null)
await transaction.CommitAsync();
await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
}
catch
{
//Just re-throw for now, let exception handler deal, but in future may want to deal with this more here
throw;
}
return true;
await NotifyEventProcessor.HandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject);
}
catch
{
//Just re-throw for now, let exception handler deal, but in future may want to deal with this more here
//no need to rollback, the transaction will rollback automatically if it's disposed without committing
throw;
}
//Note: Transaction does not need to be disposed it will automatically when it goes out of scope due to Using statement
return true;
}
@@ -862,7 +868,7 @@ namespace AyaNova.Biz
if (SaveIt)
{
o = await PutAsync(o);
if (o == null)
await JobsBiz.LogJobAsync(job.GId, $"Error processing item {id}: {GetErrorsAsString()}");

View File

@@ -1616,6 +1616,7 @@
"ErrorAPI2207": "Das Startdatum muss vor dem Enddatum liegen",
"ErrorAPI2208": "Dieses Objekt ist mit anderen verknüpft und kann auf diese Weise nicht geändert werden",
"ErrorAPI2209": "Dieser Wert kann nicht geändert werden",
"ErrorAPI2210": "Untergeordneter Objektfehler",
"ErrorServerUnresponsive": "Der Server reagiert nicht (E17)",
"ErrorUserNotAuthenticated": "Nicht authentifiziert (E16)",
"ErrorUserNotAuthorized": "Nicht autorisiert",

View File

@@ -1616,6 +1616,7 @@
"ErrorAPI2207": "Start date must be before end date",
"ErrorAPI2208": "This object is linked to others and can not be changed this way",
"ErrorAPI2209": "This value cannot be changed",
"ErrorAPI2210": "Child object error",
"ErrorServerUnresponsive": "Server not responding (E17)",
"ErrorUserNotAuthenticated": "Not authenticated (E16)",
"ErrorUserNotAuthorized": "Not authorized",

View File

@@ -1616,6 +1616,7 @@
"ErrorAPI2207": "La fecha de inicio debe ser anterior a la fecha de finalización",
"ErrorAPI2208": "Este objeto está vinculado a otros y no puede ser cambiado de esta manera",
"ErrorAPI2209": "Este valor no puede ser cambiado",
"ErrorAPI2210": "Error de objeto secundario",
"ErrorServerUnresponsive": "El servidor no responde (E17)",
"ErrorUserNotAuthenticated": "No autenticado (E16)",
"ErrorUserNotAuthorized": "No autorizado",

View File

@@ -1616,6 +1616,7 @@
"ErrorAPI2207": "La date de début doit être antérieure à la date de fin",
"ErrorAPI2208": "Cet objet est lié à d'autres et ne peut pas être modifié de cette façon",
"ErrorAPI2209": "Cette valeur ne peut pas être changée",
"ErrorAPI2210": "Erreur d'objet enfant",
"ErrorServerUnresponsive": "Le serveur ne répond pas (E17)",
"ErrorUserNotAuthenticated": "Non authentifié (E16)",
"ErrorUserNotAuthorized": "Non autorisé",