diff --git a/server/AyaNova/DataList/DataListFieldDefinition.cs b/server/AyaNova/DataList/DataListFieldDefinition.cs index a5551ddd..a9668687 100644 --- a/server/AyaNova/DataList/DataListFieldDefinition.cs +++ b/server/AyaNova/DataList/DataListFieldDefinition.cs @@ -53,6 +53,9 @@ namespace AyaNova.DataList [JsonIgnore] public string SqlColorColumnName { get; set; }//column to fetch the color if applicable to this field + [JsonIgnore] + public string SqlColumnExpression { get; set; }//column is based on this expression, not directly on the source tables + public DataListFieldDefinition() { //most common defaults @@ -65,6 +68,7 @@ namespace AyaNova.DataList AType = (int)AyaType.NoType; SqlATypeColumnName = null;//must be null as that is checked against specifically SqlColorColumnName = null;//must be null to be ignored properly + SqlColumnExpression = null; } //Get column to query for display name or use FieldName if there is no difference diff --git a/server/AyaNova/DataList/DataListSqlSelectBuilder.cs b/server/AyaNova/DataList/DataListSqlSelectBuilder.cs index c9c2e3a1..8ecd28c8 100644 --- a/server/AyaNova/DataList/DataListSqlSelectBuilder.cs +++ b/server/AyaNova/DataList/DataListSqlSelectBuilder.cs @@ -58,7 +58,11 @@ namespace AyaNova.DataList { if (firstColumnAdded) sb.Append(", "); - sb.Append(valueColumnName); + + if (o.SqlColumnExpression != null)//if there is an expression defined then add it into the select statement, the valuecolumnname will be the AS alias of it + sb.Append(o.SqlColumnExpression); + else + sb.Append(valueColumnName); firstColumnAdded = true; map.Add(valueColumnName, nOrdinal++); } diff --git a/server/AyaNova/DataList/WorkOrderDataList.cs b/server/AyaNova/DataList/WorkOrderDataList.cs index ec277cf1..3f9eac4f 100644 --- a/server/AyaNova/DataList/WorkOrderDataList.cs +++ b/server/AyaNova/DataList/WorkOrderDataList.cs @@ -11,7 +11,6 @@ namespace AyaNova.DataList { DefaultListAType = AyaType.WorkOrder; SQLFrom = "from aworkorder " - // + "left join lateral (select id, workorderstatusid, workorderid from aworkorderstate st where workorderid = aworkorder.id order by st.id DESC limit 1) st on aworkorder.id=st.workorderid " + "left join aworkorderstatus on (aworkorder.laststatusid = aworkorderstatus.id) " + "left join acustomer on (aworkorder.customerid=acustomer.id) " + "left join aheadoffice on (acustomer.headofficeid=aheadoffice.id) " @@ -19,7 +18,7 @@ namespace AyaNova.DataList + "left join acontract on (aworkorder.contractid=acontract.id)"; var RoleSet = BizRoles.GetRoleSet(DefaultListAType); AllowedRoles = RoleSet.ReadFullRecord | RoleSet.Change; - DefaultColumns = new List() { "WorkOrderSerialNumber", "Customer", "WorkOrderServiceDate", "WorkOrderCloseByDate", "WorkOrderStatus", "Project" }; + DefaultColumns = new List() { "WorkOrderSerialNumber", "Customer", "WorkOrderServiceDate", "WorkOrderCloseByDate", "WorkOrderStatus", "Project", "WorkOrderAge" }; DefaultSortBy = new Dictionary() { { "WorkOrderSerialNumber", "-" } }; FieldDefinitions = new List(); @@ -247,6 +246,15 @@ namespace AyaNova.DataList SqlValueColumnName = "aworkorder.longitude" }); + FieldDefinitions.Add(new DataListFieldDefinition + { + TKey = "WorkOrderAge", + FieldKey = "WorkOrderAge", + UiFieldDataType = (int)UiFieldDataType.TimeSpan, + SqlColumnExpression="AGE(timezone('UTC', now()), aworkorder.createddate) as expwoage", + SqlValueColumnName = "expwoage" + }); + /* diff --git a/server/AyaNova/biz/WorkOrderBiz.cs b/server/AyaNova/biz/WorkOrderBiz.cs index b5762477..fc74e50b 100644 --- a/server/AyaNova/biz/WorkOrderBiz.cs +++ b/server/AyaNova/biz/WorkOrderBiz.cs @@ -115,7 +115,7 @@ namespace AyaNova.Biz { await WorkOrderBizActionsAsync(AyaEvent.Created, newObject, null, null); newObject.Tags = TagBiz.NormalizeTags(newObject.Tags); - newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); + newObject.CustomFields = JsonUtil.CompactJson(newObject.CustomFields); await ct.WorkOrder.AddAsync(newObject); await ct.SaveChangesAsync(); await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct); @@ -163,7 +163,7 @@ namespace AyaNova.Biz await transaction.CommitAsync(); if (populateViz) await WorkOrderPopulateVizFields(newObject, true, false); - + await WorkOrderHandlePotentialNotificationEvent(AyaEvent.Created, newObject); return newObject; } @@ -416,14 +416,9 @@ namespace AyaNova.Biz //CREATED OR MODIFIED - if(ayaEvent==AyaEvent.Created || ayaEvent==AyaEvent.Modified) + if (ayaEvent == AyaEvent.Created || ayaEvent == AyaEvent.Modified) { - //Set duration to completed - //Clear it if set and not completed state - //or - //Set it if not set and completed state - todo } @@ -433,7 +428,7 @@ namespace AyaNova.Biz await AutoSetContractAsync(newObj); if (newObj.CompleteByDate == null)//need to account for a user manually selecting a specific close by date in advance indicating to ignore any auto sets await AutoSetCloseByDateAsync(newObj); - await AutoSetAddressAsync(newObj); + await AutoSetAddressAsync(newObj); } //MODIFIED ACTIONS @@ -448,6 +443,22 @@ namespace AyaNova.Biz if (newObj.ContractId != oldObj.ContractId) await AutoSetCloseByDateAsync(newObj); + + //Set duration to completed + //Note that it's not practical to set this in the Created path because the header is saved *before* the state from the client for new work orders + //by necessity so if a user creates and closes a work order in one go then duration will not be set (or at least it will remain at zero which is actually accurate) + //Clear it if set and not completed state + //or + //Set it if not set and completed state + WorkOrderStatus currentStatus = await GetCurrentWorkOrderStatusFromRelatedAsync(AyaType.WorkOrder, newObj.Id); + if (currentStatus.Completed && newObj.DurationToCompleted == TimeSpan.Zero) + { + newObj.DurationToCompleted = DateTime.UtcNow - newObj.CreatedDate; + } + else if (newObj.DurationToCompleted != TimeSpan.Zero && !currentStatus.Completed) + { + newObj.DurationToCompleted = TimeSpan.Zero; + } } } @@ -771,10 +782,11 @@ namespace AyaNova.Biz AddError(ApiErrorCode.VALIDATION_NOT_CHANGEABLE, "generalerror", await Translate("WorkOrderErrorLocked")); return;//this is a completely disqualifying error } - } + + /* todo: workorder status list first, it's a table of created items, keep properties from v7 but add the following properties: @@ -6509,7 +6521,7 @@ namespace AyaNova.Biz internal async Task GetCurrentWorkOrderStatusFromRelatedAsync(AyaType ayaType, long id) { if (mCurrentWorkOrderStatus == null) - { + { var wid = await GetWorkOrderIdFromRelativeAsync(ayaType, id, ct); var stat = await ct.WorkOrderState.AsNoTracking() .Where(z => z.WorkOrderId == wid.WorkOrderId) diff --git a/server/AyaNova/models/WorkOrder.cs b/server/AyaNova/models/WorkOrder.cs index 69f15b7b..57d0bd7e 100644 --- a/server/AyaNova/models/WorkOrder.cs +++ b/server/AyaNova/models/WorkOrder.cs @@ -45,6 +45,7 @@ namespace AyaNova.Models public long? FromQuoteId { get; set; } public long? FromPMId { get; set; } public long? FromCSRId { get; set; } + public DateTime CreatedDate { get; set; } = DateTime.UtcNow; public DateTime? ServiceDate { get; set; } public DateTime? CompleteByDate { get; set; } public TimeSpan DurationToCompleted { get; set; } = TimeSpan.Zero; @@ -119,6 +120,8 @@ namespace AyaNova.Models //workaround for notification [NotMapped, JsonIgnore] public string Name { get; set; } + + }//eoc }//eons diff --git a/server/AyaNova/util/AySchema.cs b/server/AyaNova/util/AySchema.cs index b8d2aaf0..2fc67b46 100644 --- a/server/AyaNova/util/AySchema.cs +++ b/server/AyaNova/util/AySchema.cs @@ -22,16 +22,16 @@ namespace AyaNova.Util //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! private const int DESIRED_SCHEMA_LEVEL = 1; - internal const long EXPECTED_COLUMN_COUNT = 962; + internal const long EXPECTED_COLUMN_COUNT = 963; internal const long EXPECTED_INDEX_COUNT = 138; - internal const long EXPECTED_CHECK_CONSTRAINTS = 415; + internal const long EXPECTED_CHECK_CONSTRAINTS = 416; internal const long EXPECTED_FOREIGN_KEY_CONSTRAINTS = 118; internal const long EXPECTED_VIEWS = 6; internal const long EXPECTED_ROUTINES = 2; //!!!!WARNING: BE SURE TO UPDATE THE DbUtil::EmptyBizDataFromDatabaseForSeedingOrImporting WHEN NEW TABLES ADDED!!!! - ///////////////////////////////////////////////////////////////// (C961:I137:CC415:FC118:V6:R2) + ///////////////////////////////////////////////////////////////// C963:I138:CC416:FC118:V6:R2 /* @@ -776,7 +776,8 @@ $BODY$ LANGUAGE PLPGSQL STABLE"); //WORKORDER await ExecQueryAsync("CREATE TABLE aworkorder (id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, serial BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, " + "notes TEXT, wiki TEXT, customfields TEXT, tags VARCHAR(255) ARRAY, customerid BIGINT NOT NULL REFERENCES acustomer (id), " - + "projectid BIGINT REFERENCES aproject, laststatusid BIGINT REFERENCES aworkorderstatus(id), contractid BIGINT NULL, internalreferencenumber text, customerreferencenumber text, customercontactname text, " + + "projectid BIGINT REFERENCES aproject, laststatusid BIGINT REFERENCES aworkorderstatus(id), contractid BIGINT NULL, internalreferencenumber text, " + +" customerreferencenumber text, customercontactname text, createddate TIMESTAMP NOT NULL, " + "servicedate TIMESTAMP, completebydate TIMESTAMP, invoicenumber TEXT, customersignature TEXT, customersignaturename TEXT, customersignaturecaptured TIMESTAMP, " + "techsignature TEXT, techsignaturename TEXT, techsignaturecaptured TIMESTAMP, durationtocompleted INTERVAL NOT NULL, onsite BOOL NOT NULL, " + "postaddress TEXT, postcity TEXT, postregion TEXT, postcountry TEXT, postcode TEXT, address TEXT, city TEXT, region TEXT, country TEXT, latitude DECIMAL(9,6), longitude DECIMAL(9,6) "