Skip to main content
Solved

Value not Saving to Database Because of Default Being Null


Forum|alt.badge.img

Related to this post

I am working on setting the attributes on a service order when creating a service order from an opportunity by pulling what is set on the opportunity. I have gotten it to set the value and work as long as you click “Create and Review”, and then save. The problem is when you click create, the cache is lost and the value does not save. I then tried adding the Persist action to my code, but was getting the error that the attribute value cannot be empty. There was very clearly a value being set because I didn’t modify any other code.

I came to find out that if the attribute is required, it can’t save, but if the attribute is not required, it can save and the code works fine. It seems like, it is pulling the default value set for the attribute under the service order type, then saving, then getting my value and saving on top of that because if I set it required with a default value specified, it also works. When it is required and no default value set, it tries to save a null value into a required field and breaks before it gets to my value. 

Is there a way to stop it from saving the default value first or make it not required in this instance?

public delegate void CreateDocumentDelegate(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details);

[PXOverride]
public void CreateDocument(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details, CreateDocumentDelegate baseMethod)
{
    baseMethod(srvOrdGraph, apptGraph, header, details);

    var attributes = srvOrdGraph.Answers.Select()?.FirstTableItems;

    CSAnswers attribute = attributes.FirstOrDefault(a => a.AttributeID == "SUBSERVICE");

    var subservice = (PXStringState)Base.OpportunityCurrent.Cache.GetValueExt(Base.OpportunityCurrent.Current, "SUBSERVICE_Attributes");

    if (attribute is object)
    {
        attribute.Value = subservice;
        srvOrdGraph.Answers.Update(attribute);
        srvOrdGraph.Persist();
    }
}

 

Best answer by DrewNisley

Ended up going a different direction with it by adding a RowPersisting event directly to the ServiceOrderEntry graph with logic on whether to run the base RowPersisting events or not.

// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class FSServiceOrderGraphExtension : PXGraphExtension<ServiceOrderEntry>
{
    protected virtual void _(Events.RowPersisting<CSAnswers> e, PXRowPersisting b)
    {
        FSServiceOrder svcOrd = Base.ServiceOrderRecords.Current;
        if (svcOrd == null) return;
        CSAnswers row = e.Row;
        if (row == null) return;

        //Put logic in this if statement
        if (svcOrd.SourceReferenceNbr != null && row.AttributeID == "SUBSERVICE" && row.IsRequired == true)
        {
            e.Cancel = false;

        }
        else
        {
            b?.Invoke(e.Cache, e.Args);
        }
    }
}

public class DialogBoxSOApptCreation_Ext : PXGraphExtension<SM_OpportunityMaint_DBox, SM_OpportunityMaint, OpportunityMaint>
{
    public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.serviceManagementModule>();

    #region Copy Opportunity Attribute to Service Order
    public delegate void CreateDocumentDelegate(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details);
    [PXOverride]
    public void CreateDocument(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details, CreateDocumentDelegate baseMethod)
    {
        baseMethod(srvOrdGraph, apptGraph, header, details);

        var subservice = (PXStringState)Base.OpportunityCurrent.Cache.GetValueExt(Base.OpportunityCurrent.Current, "SUBSERVICE_Attributes");
        var subserviceValue = subservice?.Value as string;

        var existingAnswer = srvOrdGraph.Answers.Select().FirstTableItems
                    .FirstOrDefault(a => a.AttributeID == "SUBSERVICE" && a.RefNoteID == srvOrdGraph.ServiceOrderRecords.Current.NoteID);

        if (existingAnswer != null)
        {
            existingAnswer.Value = subserviceValue;
            srvOrdGraph.Answers.Update(existingAnswer);
        }

        try
        {
            srvOrdGraph.Persist();
        }
        catch (Exception ex)
        {
            PXTrace.WriteError($"Error during Save.Press: {ex.Message}");
                    throw;
        }                
    }
    #endregion
}

 

View original
Did this topic help you find an answer to your question?

3 replies

Forum|alt.badge.img
  • Author
  • Semi-Pro I
  • 32 replies
  • January 10, 2025

Decided to revisit this today and I still don’t know why it has to save the default first, but I think if I can remove these two RowPersisting handlers in the CRAttributeList graph, then I can bypass it not being able to save a null value.

Source Code

public CRAttributeList(PXGraph graph)
{
    _Graph = graph;
    _helper = new EntityHelper(graph);
    View = (graph.IsExport ? GetExportOptimizedView() : new PXView(graph, isReadOnly: false, new Select3<CSAnswers, OrderBy<Asc<CSAnswers.order>>>(), new PXSelectDelegate(SelectDelegate)));
    PXDependToCacheAttribute.AddDependencies(View, new Type[1] { typeof(TEntity) });
    _Graph.EnsureCachePersistence(typeof(CSAnswers));
    PXDBAttributeAttribute.Activate(_Graph.Caches[typeof(TEntity)]);
    _Graph.FieldUpdating.AddHandler<CSAnswers.value>(FieldUpdatingHandler);
    _Graph.FieldSelecting.AddHandler<CSAnswers.value>(FieldSelectingHandler);
    _Graph.FieldSelecting.AddHandler<CSAnswers.isRequired>(IsRequiredSelectingHandler);
    _Graph.FieldSelecting.AddHandler<CSAnswers.attributeCategory>(AttributeCategorySelectingHandler);
    _Graph.FieldSelecting.AddHandler<CSAnswers.attributeID>(AttrFieldSelectingHandler);
    _Graph.RowPersisting.AddHandler<CSAnswers>(RowPersistingHandler);
    _Graph.RowPersisting.AddHandler<TEntity>(ReferenceRowPersistingHandler);
    _Graph.RowUpdating.AddHandler<TEntity>(ReferenceRowUpdatingHandler);
    _Graph.RowDeleted.AddHandler<TEntity>(ReferenceRowDeletedHandler);
    _Graph.RowInserted.AddHandler<TEntity>(RowInsertedHandler);
    _Graph.Caches<CSAnswers>().Fields.Add("Value$value");
    _Graph.Caches<CSAnswers>().Fields.Add("AttributeID$value");
    _Graph.FieldSelecting.AddHandler(typeof(CSAnswers), "Value$value", CbApiValueFieldSelectingHandler);
    _Graph.FieldSelecting.AddHandler(typeof(CSAnswers), "AttributeID$value", CbApiAttributeIdFieldSelectingHandler);
}

protected virtual void ReferenceRowPersistingHandler(PXCache sender, PXRowPersistingEventArgs e)
{
    object row = e.Row;
    if (row == null)
    {
        return;
    }

    PXCache answers = GetAnswers();
    string classId = GetClassId(row);
    CRAttribute.ClassAttributeList classAttributeList = new CRAttribute.ClassAttributeList();
    if (classId != null)
    {
        classAttributeList = CRAttribute.EntityAttributes(GetEntityTypeFromAttribute(row), classId);
        (from _ in classAttributeList
         where _.NotInClass
         select _.ID).ToList().ForEach(delegate (string _)
     {
         classAttributeList.Remove(_);
     });
    }

    List<string> list = new List<string>();
    foreach (CSAnswers item in answers.Cached)
    {
        if (!item.IsRequired.HasValue && View.Cache.GetValueExt<CSAnswers.isRequired>(item) is PXFieldState pXFieldState)
        {
            item.IsRequired = pXFieldState.Value as bool?;
        }

        CRAttribute.AttributeExt value;
        if (e.Operation == PXDBOperation.Delete)
        {
            answers.Delete(item);
        }
        else if (string.IsNullOrEmpty(item.Value) && item.IsRequired == true && (!_Graph.UnattendedMode || ForceValidationInUnattendedMode) && (!classAttributeList.TryGetValue(item.AttributeID, out value) || value.IsActive))
        {
            string text = "";
            CRAttribute.Attribute attribute = CRAttribute.Attributes[item.AttributeID];
            if (attribute != null)
            {
                text = attribute.Description;
            }

            list.Add(text);
            string text2 = PXMessages.LocalizeFormatNoPrefix("'{0}' cannot be empty.", text);
            answers.RaiseExceptionHandling<CSAnswers.value>(item, item.Value, new PXSetPropertyException(text2, PXErrorLevel.RowError, typeof(CSAnswers.value).Name));
            PXUIFieldAttribute.SetError<CSAnswers.value>(answers, item, text2);
        }
    }

    if (list.Count <= 0)
    {
        return;
    }

    Dictionary<string, string> dictionary = new Dictionary<string, string>();
    dictionary.Add("1", PXMessages.LocalizeFormatNoPrefix("There are empty required attributes: {0}", string.Join(", ", list.Select((string s) => $"'{s}'"))));
    throw new PXOuterException(dictionary, _Graph.GetType(), row, "There are empty required attributes: {0}", string.Join(", ", list.Select((string s) => $"'{s}'")));
}

protected virtual void RowPersistingHandler(PXCache sender, PXRowPersistingEventArgs e)
{
    if ((e.Operation != PXDBOperation.Insert && e.Operation != PXDBOperation.Update) || !(e.Row is CSAnswers cSAnswers))
    {
        return;
    }

    if (!cSAnswers.RefNoteID.HasValue)
    {
        e.Cancel = true;
        RowPersistDeleted(sender, cSAnswers);
    }
    else if (string.IsNullOrEmpty(cSAnswers.Value) && cSAnswers.IsActive == true)
    {
        string format = PXMessages.LocalizeFormatNoPrefix("'{0}' cannot be empty.", sender.GetStateExt<CSAnswers.value>(null).With((object _) => _ as PXFieldState).With((PXFieldState _) => _.DisplayName));
        if (cSAnswers.IsRequired == true && sender.RaiseExceptionHandling<CSAnswers.value>(e.Row, cSAnswers.Value, new PXSetPropertyException(format, PXErrorLevel.RowError, typeof(CSAnswers.value).Name)))
        {
            throw new PXRowPersistingException(typeof(CSAnswers.value).Name, cSAnswers.Value, format, typeof(CSAnswers.value).Name);
        }

        e.Cancel = true;
        if (sender.GetStatus(cSAnswers) != PXEntryStatus.Inserted)
        {
            RowPersistDeleted(sender, cSAnswers);
        }
    }
}

My Code

        public class DialogBoxSOApptCreation_Ext : PXGraphExtension<SM_OpportunityMaint_DBox, SM_OpportunityMaint, OpportunityMaint>
        {
            public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.serviceManagementModule>();

            #region Copy Opportunity Attribute to Service Order
            public delegate void CreateDocumentDelegate(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details);
            [PXOverride]
            public void CreateDocument(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details, CreateDocumentDelegate baseMethod)
            {
                baseMethod(srvOrdGraph, apptGraph, header, details);

                var subservice = (PXStringState)Base.OpportunityCurrent.Cache.GetValueExt(Base.OpportunityCurrent.Current, "SUBSERVICE_Attributes");
                var subserviceValue = subservice?.Value as string;

                var existingAnswer = srvOrdGraph.Answers.Select().FirstTableItems
                    .FirstOrDefault(a => a.AttributeID == "SUBSERVICE" && a.RefNoteID == srvOrdGraph.ServiceOrderRecords.Current.NoteID);

                if (existingAnswer != null)
                {
                    existingAnswer.Value = subserviceValue;
                    srvOrdGraph.Answers.Update(existingAnswer);
                }
                RemoveRowPersistingHandler(srvOrdGraph);
                try
                {
                    srvOrdGraph.Save.Press();
                }
                catch (Exception ex)
                {
                    PXTrace.WriteError($"Error during Save.Press: {ex.Message}");
                    throw;
                }                
            }
            #endregion


            private void RemoveRowPersistingHandler(ServiceOrderEntry srvOrdGraph)
            {
                // Access the Answers view in ServiceOrderEntry
                var answersField = srvOrdGraph.GetType().GetField("Answers", BindingFlags.Instance | BindingFlags.NonPublic);
                if (answersField == null)
                {
                    PXTrace.WriteInformation("Answers view not found.");
                    return;
                }

                // Get the view object
                var answersView = answersField.GetValue(srvOrdGraph) as PXView;
                if (answersView == null)
                {
                    PXTrace.WriteInformation("Answers view object not found.");
                    return;
                }

                // Locate and remove the RowPersistingHandler
                var rowPersistingHandler = answersView.GetType().GetMethod("RowPersistingHandler", BindingFlags.Instance | BindingFlags.NonPublic);
                if (rowPersistingHandler != null)
                {
                    var rowPersistingDelegate = Delegate.CreateDelegate(typeof(PXRowPersisting), answersView, rowPersistingHandler) as PXRowPersisting;
                    srvOrdGraph.RowPersisting.RemoveHandler<CSAnswers>(rowPersistingDelegate);
                }

                // Locate and remove the ReferenceRowPersistingHandler
                var referenceRowPersistingHandler = answersView.GetType().GetMethod("ReferenceRowPersistingHandler", BindingFlags.Instance | BindingFlags.NonPublic);
                if (referenceRowPersistingHandler != null)
                {
                    var referenceRowPersistingDelegate = Delegate.CreateDelegate(typeof(PXRowPersisting), answersView, referenceRowPersistingHandler) as PXRowPersisting;
                    srvOrdGraph.RowPersisting.RemoveHandler<FSServiceOrder>(referenceRowPersistingDelegate);
                }
            }
        }

I thought this should work to remove those handlers and allow it to save a null value, further allowing me to save my value on top of it, but I am still getting the same error. Anyone have anything they can offer?


Forum|alt.badge.img
  • Author
  • Semi-Pro I
  • 32 replies
  • Answer
  • January 10, 2025

Ended up going a different direction with it by adding a RowPersisting event directly to the ServiceOrderEntry graph with logic on whether to run the base RowPersisting events or not.

// Acuminator disable once PX1016 ExtensionDoesNotDeclareIsActiveMethod extension should be constantly active
public class FSServiceOrderGraphExtension : PXGraphExtension<ServiceOrderEntry>
{
    protected virtual void _(Events.RowPersisting<CSAnswers> e, PXRowPersisting b)
    {
        FSServiceOrder svcOrd = Base.ServiceOrderRecords.Current;
        if (svcOrd == null) return;
        CSAnswers row = e.Row;
        if (row == null) return;

        //Put logic in this if statement
        if (svcOrd.SourceReferenceNbr != null && row.AttributeID == "SUBSERVICE" && row.IsRequired == true)
        {
            e.Cancel = false;

        }
        else
        {
            b?.Invoke(e.Cache, e.Args);
        }
    }
}

public class DialogBoxSOApptCreation_Ext : PXGraphExtension<SM_OpportunityMaint_DBox, SM_OpportunityMaint, OpportunityMaint>
{
    public static bool IsActive() => PXAccess.FeatureInstalled<FeaturesSet.serviceManagementModule>();

    #region Copy Opportunity Attribute to Service Order
    public delegate void CreateDocumentDelegate(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details);
    [PXOverride]
    public void CreateDocument(ServiceOrderEntry srvOrdGraph, AppointmentEntry apptGraph, DBoxHeader header, List<DBoxDetails> details, CreateDocumentDelegate baseMethod)
    {
        baseMethod(srvOrdGraph, apptGraph, header, details);

        var subservice = (PXStringState)Base.OpportunityCurrent.Cache.GetValueExt(Base.OpportunityCurrent.Current, "SUBSERVICE_Attributes");
        var subserviceValue = subservice?.Value as string;

        var existingAnswer = srvOrdGraph.Answers.Select().FirstTableItems
                    .FirstOrDefault(a => a.AttributeID == "SUBSERVICE" && a.RefNoteID == srvOrdGraph.ServiceOrderRecords.Current.NoteID);

        if (existingAnswer != null)
        {
            existingAnswer.Value = subserviceValue;
            srvOrdGraph.Answers.Update(existingAnswer);
        }

        try
        {
            srvOrdGraph.Persist();
        }
        catch (Exception ex)
        {
            PXTrace.WriteError($"Error during Save.Press: {ex.Message}");
                    throw;
        }                
    }
    #endregion
}

 


Chris Hackett
Community Manager
Forum|alt.badge.img
  • Acumatica Community Manager
  • 2657 replies
  • January 10, 2025

Thank you for sharing your solution with the community ​@DrewNisley!


Reply


Cookie policy

We use cookies to enhance and personalize your experience. If you accept you agree to our full cookie policy. Learn more about our cookies.

 
Cookie settings