diff --git a/.vscode/launch.json b/.vscode/launch.json index fcc80a75..b77f3b95 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -53,7 +53,7 @@ "AYANOVA_FOLDER_USER_FILES": "c:\\temp\\RavenTestData\\userfiles", "AYANOVA_FOLDER_BACKUP_FILES": "c:\\temp\\RavenTestData\\backupfiles", "AYANOVA_FOLDER_TEMPORARY_SERVER_FILES": "c:\\temp\\RavenTestData\\tempfiles", - "AYANOVA_SERVER_TEST_MODE": "true", + "AYANOVA_SERVER_TEST_MODE": "false", "AYANOVA_SERVER_TEST_MODE_SEEDLEVEL": "small", "AYANOVA_SERVER_TEST_MODE_TZ_OFFSET": "-7", "AYANOVA_BACKUP_PG_DUMP_PATH": "C:\\data\\code\\postgres_13\\bin\\" diff --git a/docs/8.0/ayanova/docs/svc-pms.md b/docs/8.0/ayanova/docs/svc-pms.md index 5466bc27..218fb863 100644 --- a/docs/8.0/ayanova/docs/svc-pms.md +++ b/docs/8.0/ayanova/docs/svc-pms.md @@ -21,6 +21,10 @@ If an date is left empty it will not be affected by this process so for example When a PM is first created it will use the exact service date chosen the **first** time it generates the first workorder regardless of which day it falls on as it's assumed the user specifically wants that particular day for the first Work order generated from that PM. Subsequent generate events will take into account the chosen excluded days. +### Schedule conflicts handling + +Normally if the Global setting "Allow schedule conflicts" is set to False then a work order item scheduled user record can not be saved if there is a schedule conflict. In the case of a PM automatically generating a work order however, this could prevent the entire Work order from being generated. For this reason the normal rules are suspended when generating a work order and it will save the scheduled user record even if it conflicts. + ### Inventory handling AyaNova removes inventory from stock immediately upon a Work order item Part record being saved and the same is true if the Work order is generated by a PM at the server automatically. diff --git a/server/AyaNova/biz/PMBiz.cs b/server/AyaNova/biz/PMBiz.cs index 01df19eb..c8643aa6 100644 --- a/server/AyaNova/biz/PMBiz.cs +++ b/server/AyaNova/biz/PMBiz.cs @@ -4790,11 +4790,8 @@ namespace AyaNova.Biz //process those pms foreach (long pmid in l) { - log.LogDebug($"processing pm id {pmid}"); - - //look for same delivery already made and skip if already notified (sb one time only but will repeat for > 90 days as delivery log gets pruned) if (await ct.NotifyDeliveryLog.AnyAsync(z => z.NotifySubscriptionId == sub.Id && z.ObjectId == pmid)) { @@ -5125,6 +5122,7 @@ namespace AyaNova.Biz wois.StopDate = pmsu.StopDate;//DATE ADJUST wois.Tags = pmsu.Tags; wois.UserId = pmsu.UserId; + wois.IsPMGenerated = true;//signifies to ignore schedule conflicts woi.ScheduledUsers.Add(wois); } diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index 9ebd77bf..3884a428 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -2369,7 +2369,7 @@ namespace AyaNova.Biz await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); await ExpenseSearchIndexAsync(newObject, true); - + await ExpenseHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await ExpensePopulateVizFields(newObject); return newObject; @@ -2451,8 +2451,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await ExpenseHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -2732,13 +2732,13 @@ namespace AyaNova.Biz return null; else { - + await ct.WorkOrderItemLabor.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); await LaborSearchIndexAsync(newObject, true); - + await LaborHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await LaborPopulateVizFields(newObject); return newObject; @@ -2797,7 +2797,7 @@ namespace AyaNova.Biz } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, putObject.AyaType, AyaEvent.Modified), ct); await LaborSearchIndexAsync(putObject, false); - + await LaborHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await LaborPopulateVizFields(putObject); return putObject; @@ -2821,8 +2821,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await LaborHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -3048,7 +3048,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -3139,7 +3139,7 @@ namespace AyaNova.Biz await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); await LoanSearchIndexAsync(newObject, true); - + await LoanHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await LoanPopulateVizFields(newObject); return newObject; @@ -3195,7 +3195,7 @@ namespace AyaNova.Biz } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, putObject.AyaType, AyaEvent.Modified), ct); await LoanSearchIndexAsync(putObject, false); - + await LoanHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await LoanPopulateVizFields(putObject); return putObject; @@ -3219,8 +3219,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await LoanHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -3452,7 +3452,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -3546,12 +3546,12 @@ namespace AyaNova.Biz else { // newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); - + await ct.WorkOrderItemOutsideService.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); await OutsideServiceSearchIndexAsync(newObject, true); - + await OutsideServiceHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await OutsideServicePopulateVizFields(newObject); return newObject; @@ -3605,7 +3605,7 @@ namespace AyaNova.Biz } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, putObject.Id, putObject.AyaType, AyaEvent.Modified), ct); await OutsideServiceSearchIndexAsync(putObject, false); - + await OutsideServiceHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await OutsideServicePopulateVizFields(putObject); @@ -3630,8 +3630,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await OutsideServiceHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -3778,7 +3778,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -4017,8 +4017,8 @@ namespace AyaNova.Biz else { await PartBizActionsAsync(AyaEvent.Created, newObject, null, null); - - + + await ct.WorkOrderItemPart.AddAsync(newObject); await ct.SaveChangesAsync(); await PartInventoryAdjustmentAsync(AyaEvent.Created, newObject, null, transaction); @@ -4030,7 +4030,7 @@ namespace AyaNova.Biz await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); await PartSearchIndexAsync(newObject, true); await transaction.CommitAsync(); - + await PartPopulateVizFields(newObject); return newObject; } @@ -4070,8 +4070,8 @@ namespace AyaNova.Biz return null; } - - + + await PartValidateAsync(putObject, dbObject); if (HasErrors) return null; @@ -4097,7 +4097,7 @@ namespace AyaNova.Biz } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, putObject.Id, putObject.AyaType, AyaEvent.Modified), ct); await PartSearchIndexAsync(putObject, false); - + await PartHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await transaction.CommitAsync(); await PartPopulateVizFields(putObject); @@ -4130,8 +4130,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await PartHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -4542,7 +4542,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -4629,13 +4629,13 @@ namespace AyaNova.Biz return null; else { - - + + await ct.WorkOrderItemPartRequest.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); //await PartRequestSearchIndexAsync(newObject, true); - + await PartRequestHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await PartRequestPopulateVizFields(newObject); return newObject; @@ -4671,8 +4671,8 @@ namespace AyaNova.Biz AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } - - + + await PartRequestValidateAsync(putObject, dbObject); if (HasErrors) return null; ct.Replace(dbObject, putObject); @@ -4689,8 +4689,8 @@ namespace AyaNova.Biz return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, putObject.Id, putObject.AyaType, AyaEvent.Modified), ct); - - + + await PartRequestHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await PartRequestPopulateVizFields(putObject); return putObject; @@ -4714,8 +4714,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await PartRequestHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -4819,7 +4819,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -4905,13 +4905,13 @@ namespace AyaNova.Biz return null; else { - - + + await ct.WorkOrderItemScheduledUser.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); - - + + await ScheduledUserHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await ScheduledUserPopulateVizFields(newObject); return newObject; @@ -4948,8 +4948,8 @@ namespace AyaNova.Biz return null; } - - + + await ScheduledUserValidateAsync(putObject, dbObject); if (HasErrors) return null; ct.Replace(dbObject, putObject); @@ -4966,8 +4966,8 @@ namespace AyaNova.Biz return null; } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, putObject.AyaType, AyaEvent.Modified), ct); - - + + await ScheduledUserHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await ScheduledUserPopulateVizFields(putObject); return putObject; @@ -4991,8 +4991,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await ScheduledUserHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -5076,6 +5076,7 @@ namespace AyaNova.Biz //Scheduling conflict? if (!AyaNova.Util.ServerGlobalBizSettings.Cache.AllowScheduleConflicts + && proposedObj.IsPMGenerated == false && proposedObj.UserId != null && proposedObj.StartDate != null && proposedObj.StopDate != null @@ -5106,7 +5107,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -5306,13 +5307,13 @@ namespace AyaNova.Biz return null; else { - - + + await ct.WorkOrderItemTask.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, newObject.AyaType, AyaEvent.Created), ct); await TaskSearchIndexAsync(newObject, true); - + await TaskHandlePotentialNotificationEvent(AyaEvent.Created, newObject); await TaskPopulateVizFields(newObject); return newObject; @@ -5346,8 +5347,8 @@ namespace AyaNova.Biz AddError(ApiErrorCode.CONCURRENCY_CONFLICT); return null; } - - + + await TaskValidateAsync(putObject, dbObject); if (HasErrors) return null; ct.Replace(dbObject, putObject); @@ -5365,7 +5366,7 @@ namespace AyaNova.Biz } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, putObject.AyaType, AyaEvent.Modified), ct); await TaskSearchIndexAsync(dbObject, false); - + await TaskHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await TaskPopulateVizFields(putObject); return putObject; @@ -5389,8 +5390,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct);//Fix?? await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await TaskHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -5513,7 +5514,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -5657,7 +5658,7 @@ namespace AyaNova.Biz } await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, putObject.AyaType, AyaEvent.Modified), ct); await TravelSearchIndexAsync(putObject, false); - + await TravelHandlePotentialNotificationEvent(AyaEvent.Modified, putObject, dbObject); await TravelPopulateVizFields(putObject); return putObject; @@ -5681,8 +5682,8 @@ namespace AyaNova.Biz //Log event await EventLogProcessor.DeleteObjectLogAsync(UserId, dbObject.AyaType, dbObject.Id, "woitem:" + dbObject.WorkOrderItemId.ToString(), ct); await Search.ProcessDeletedObjectKeywordsAsync(dbObject.Id, dbObject.AyaType, ct); - - + + if (parentTransaction == null) await transaction.CommitAsync(); await TravelHandlePotentialNotificationEvent(AyaEvent.Deleted, dbObject); @@ -5893,7 +5894,7 @@ namespace AyaNova.Biz RequiredFieldsValidator.Validate(this, FormCustomization, proposedObj);//note: this is passed only to add errors //validate custom fields - + } } @@ -5976,8 +5977,8 @@ namespace AyaNova.Biz // internal async Task UnitCreateAsync(WorkOrderItemUnit newObject) { - - + + await UnitValidateAsync(newObject, null); @@ -5985,7 +5986,7 @@ namespace AyaNova.Biz return null; else { - + newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrderItemUnit.AddAsync(newObject); @@ -6193,7 +6194,7 @@ namespace AyaNova.Biz // if (ServerBootConfig.SEEDING) return; // - A work order *MUST* have only one Unit with a Contract, if there is already a unit with a contract on this workorder then a new one cannot be added and it will reject with a validation error - + //run validation and biz rules bool isNew = currentObj == null; diff --git a/server/AyaNova/models/WorkOrderItemScheduledUser.cs b/server/AyaNova/models/WorkOrderItemScheduledUser.cs index 6041ee27..4a35d87c 100644 --- a/server/AyaNova/models/WorkOrderItemScheduledUser.cs +++ b/server/AyaNova/models/WorkOrderItemScheduledUser.cs @@ -28,6 +28,12 @@ namespace AyaNova.Models [NotMapped, JsonIgnore] public string Name { get; set; } + + [NotMapped, JsonIgnore] + public bool IsPMGenerated { get; set; } = false;//INTERNAL, USED BY PM GENERATOR TO SIGNIFY IGNORE SCHEDULE CONFLICTS + + + [Required] public long WorkOrderItemId { get; set; } [JsonIgnore]