This commit is contained in:
@@ -169,96 +169,47 @@ namespace AyaNova.Biz
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ////////////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// //DUPLICATE
|
|
||||||
// //
|
|
||||||
// internal async Task<WorkOrder> WorkOrderDuplicateAsync(long id)
|
|
||||||
// {
|
|
||||||
// WorkOrder dbObject = await WorkOrderGetAsync(id, false);
|
|
||||||
// if (dbObject == null)
|
|
||||||
// {
|
|
||||||
// AddError(ApiErrorCode.NOT_FOUND, "id");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// WorkOrder newObject = new WorkOrder();
|
|
||||||
// CopyObject.Copy(dbObject, newObject, "Wiki, Serial, States");
|
|
||||||
|
|
||||||
// //walk the tree and reset all id's and concurrencies
|
|
||||||
// //TOP
|
|
||||||
// newObject.Id = 0;
|
|
||||||
// newObject.Concurrency = 0;
|
|
||||||
// foreach (var o in newObject.Items)
|
|
||||||
// {
|
|
||||||
// o.Id = 0;
|
|
||||||
// o.Concurrency = 0;
|
|
||||||
// foreach (var v in o.Expenses)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.Labors)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.Loans)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.OutsideServices)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.PartRequests)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.Parts)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.ScheduledUsers)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.Tasks)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.Travels)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// foreach (var v in o.Units)
|
|
||||||
// { v.Id = 0; v.Concurrency = 0; }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// await ct.WorkOrder.AddAsync(newObject);
|
//workorder needs to be fetched internally from several places for rule checking etc
|
||||||
// await ct.SaveChangesAsync();
|
//this just gets it raw and lets others process
|
||||||
// await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
|
private async Task<WorkOrder> WorkOrderGetFullAsync(long id)
|
||||||
// await WorkOrderSearchIndexAsync(newObject, true);
|
{
|
||||||
// await TagBiz.ProcessUpdateTagsInRepositoryAsync(ct, newObject.Tags, null);
|
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
|
||||||
// await WorkOrderPopulateVizFields(newObject, false);//doing this here ahead of notification because notification may require the viz field lookup anyway and afaict no harm in it
|
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
|
||||||
// await WorkOrderHandlePotentialNotificationEvent(AyaEvent.Created, newObject);
|
return await ct.WorkOrder.AsSplitQuery().AsNoTracking()
|
||||||
// return newObject;
|
.Include(s => s.States)
|
||||||
// }
|
.Include(w => w.Items.OrderBy(item => item.Sequence))
|
||||||
|
.ThenInclude(wi => wi.Expenses)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.Labors)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.Loans)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.Parts)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.PartRequests)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.ScheduledUsers)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.Tasks.OrderBy(t => t.Sequence))
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.Travels)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.Units)
|
||||||
|
.Include(w => w.Items)
|
||||||
|
.ThenInclude(wi => wi.OutsideServices)
|
||||||
|
.SingleOrDefaultAsync(z => z.Id == id);
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// GET
|
// GET
|
||||||
//
|
//
|
||||||
internal async Task<WorkOrder> WorkOrderGetAsync(long id, bool populateDisplayFields, bool logTheGetEvent = true)
|
internal async Task<WorkOrder> WorkOrderGetAsync(long id, bool populateDisplayFields, bool logTheGetEvent = true)
|
||||||
{
|
{
|
||||||
//Note: there could be rules checking here in future, i.e. can only get own workorder or something
|
|
||||||
//if so, then need to implement AddError and in route handle Null return with Error check just like PUT route does now
|
|
||||||
|
|
||||||
//https://docs.microsoft.com/en-us/ef/core/querying/related-data
|
var ret = await WorkOrderGetFullAsync(id);
|
||||||
//docs say this will not query twice but will recognize the duplicate woitem bit which is required for multiple grandchild collections
|
|
||||||
var ret =
|
|
||||||
await ct.WorkOrder.AsSplitQuery().AsNoTracking()
|
|
||||||
.Include(s => s.States)
|
|
||||||
.Include(w => w.Items.OrderBy(item => item.Sequence))
|
|
||||||
.ThenInclude(wi => wi.Expenses)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.Labors)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.Loans)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.Parts)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.PartRequests)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.ScheduledUsers)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.Tasks.OrderBy(t => t.Sequence))
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.Travels)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.Units)
|
|
||||||
.Include(w => w.Items)
|
|
||||||
.ThenInclude(wi => wi.OutsideServices)
|
|
||||||
.SingleOrDefaultAsync(z => z.Id == id);
|
|
||||||
|
|
||||||
if (ret != null)
|
if (ret != null)
|
||||||
{
|
{
|
||||||
@@ -2652,7 +2603,7 @@ namespace AyaNova.Biz
|
|||||||
uid: Date.now() //used for
|
uid: Date.now() //used for
|
||||||
*/
|
*/
|
||||||
if (proposedObj.ChargeAmount != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeAmount");
|
if (proposedObj.ChargeAmount != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeAmount");
|
||||||
// if (proposedObj.TaxPaid != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "TaxPaid");
|
// if (proposedObj.TaxPaid != 0) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "TaxPaid");
|
||||||
if (proposedObj.ChargeTaxCodeId != null) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeTaxCodeId");
|
if (proposedObj.ChargeTaxCodeId != null) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeTaxCodeId");
|
||||||
if (proposedObj.ReimburseUser != false) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ReimburseUser");
|
if (proposedObj.ReimburseUser != false) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ReimburseUser");
|
||||||
if (proposedObj.ChargeToCustomer != false) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeToCustomer");
|
if (proposedObj.ChargeToCustomer != false) AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "ChargeToCustomer");
|
||||||
@@ -6007,6 +5958,10 @@ namespace AyaNova.Biz
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#region WorkOrderItemUnit level
|
#region WorkOrderItemUnit level
|
||||||
|
|
||||||
|
//this is set by validation for further processing later if applicable
|
||||||
|
private Contract currentUnitContract = null;
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//EXISTS
|
//EXISTS
|
||||||
internal async Task<bool> UnitExistsAsync(long id)
|
internal async Task<bool> UnitExistsAsync(long id)
|
||||||
@@ -6019,6 +5974,10 @@ namespace AyaNova.Biz
|
|||||||
//
|
//
|
||||||
internal async Task<WorkOrderItemUnit> UnitCreateAsync(WorkOrderItemUnit newObject)
|
internal async Task<WorkOrderItemUnit> UnitCreateAsync(WorkOrderItemUnit newObject)
|
||||||
{
|
{
|
||||||
|
//todo: contract stuff and validation of no other existing contracted unit
|
||||||
|
//assumptions: this create only gets called if there is an existing woheader saved in all cases
|
||||||
|
|
||||||
|
|
||||||
await UnitValidateAsync(newObject, null);
|
await UnitValidateAsync(newObject, null);
|
||||||
if (HasErrors)
|
if (HasErrors)
|
||||||
return null;
|
return null;
|
||||||
@@ -6184,7 +6143,7 @@ namespace AyaNova.Biz
|
|||||||
|
|
||||||
//TODO: ADD VALIDATIONS:
|
//TODO: ADD VALIDATIONS:
|
||||||
// - 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
|
// - 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
|
||||||
|
// a unit record is saved only *after* there is already a header (by api users and our client software) so can easily check and set here
|
||||||
|
|
||||||
//run validation and biz rules
|
//run validation and biz rules
|
||||||
bool isNew = currentObj == null;
|
bool isNew = currentObj == null;
|
||||||
@@ -6224,6 +6183,35 @@ namespace AyaNova.Biz
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//Contracted unit? Only one per work order is allowed
|
||||||
|
if (isNew || proposedObj.UnitId != currentObj.UnitId)
|
||||||
|
{
|
||||||
|
//See if this unit has a contract, if so then see if the contract is active, if so then iterate workorder graph and check all other units for same
|
||||||
|
//if any found then reject this
|
||||||
|
var proposedContractInfo = await ct.Unit.AsNoTracking().Where(x => x.Id == proposedObj.Id).Select(x => new { x.ContractExpires, x.ContractId }).FirstOrDefaultAsync();
|
||||||
|
if (proposedContractInfo.ContractId != null && proposedContractInfo.ContractExpires > DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
//added woitemunit has a contract and apparently unexpired so need to check if contract is still active
|
||||||
|
currentUnitContract = await GetFullyPopulatedContractGraphFromIdAsync(proposedContractInfo.ContractId);
|
||||||
|
if (currentUnitContract != null && currentUnitContract.Active)
|
||||||
|
{
|
||||||
|
//iterate work order and check for other contracted unit
|
||||||
|
var woId = await GetWorkOrderIdFromRelativeAsync(AyaType.WorkOrderItem, proposedObj.WorkOrderItemId, ct);
|
||||||
|
var w = await WorkOrderGetFullAsync(woId.WorkOrderId);
|
||||||
|
|
||||||
|
//iterate, look for *other* woitemunit records, are they contracted already?
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentUnitContract = null;//just in case it's non active but present so later biz actions don't process it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//check if there is another contracted unit already on this work order
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Any form customizations to validate?
|
//Any form customizations to validate?
|
||||||
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemUnit.ToString());
|
var FormCustomization = await ct.FormCustom.AsNoTracking().SingleOrDefaultAsync(z => z.FormKey == AyaType.WorkOrderItemUnit.ToString());
|
||||||
@@ -6433,17 +6421,24 @@ namespace AyaNova.Biz
|
|||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
if (mFetchedContractAlready == false)
|
if (mFetchedContractAlready == false)
|
||||||
{
|
{
|
||||||
mContractInEffect = await ct.Contract.AsSplitQuery().AsNoTracking()
|
mContractInEffect = await GetFullyPopulatedContractGraphFromIdAsync(id);
|
||||||
.Include(c => c.ServiceRateItems)
|
|
||||||
.Include(c => c.TravelRateItems)
|
|
||||||
.Include(c => c.ContractPartOverrideItems)
|
|
||||||
.Include(c => c.ContractTravelRateOverrideItems)
|
|
||||||
.Include(c => c.ContractServiceRateOverrideItems)
|
|
||||||
.FirstOrDefaultAsync(z => z.Id == id);
|
|
||||||
}
|
}
|
||||||
return mContractInEffect;
|
return mContractInEffect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal async Task<Contract> GetFullyPopulatedContractGraphFromIdAsync(long? id)
|
||||||
|
{
|
||||||
|
if (id == null) return null;
|
||||||
|
return await ct.Contract.AsSplitQuery().AsNoTracking()
|
||||||
|
.Include(c => c.ServiceRateItems)
|
||||||
|
.Include(c => c.TravelRateItems)
|
||||||
|
.Include(c => c.ContractPartOverrideItems)
|
||||||
|
.Include(c => c.ContractTravelRateOverrideItems)
|
||||||
|
.Include(c => c.ContractServiceRateOverrideItems)
|
||||||
|
.FirstOrDefaultAsync(z => z.Id == id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//GET CURRENT STATUS FOR WORKORDER FROM RELATIVE
|
//GET CURRENT STATUS FOR WORKORDER FROM RELATIVE
|
||||||
|
|||||||
Reference in New Issue
Block a user