【问题标题】:Copy Customer Attributes to Sales Order Attributes将客户属性复制到销售订单属性
【发布时间】:2018-01-18 08:29:21
【问题描述】:

在客户屏幕中,我根据客户类配置了一些属性

当我为该客户提出建议时,我希望将这些属性复制到销售订单中。示例屏幕如下:

销售订单属性网格将是只读的。 我知道如何添加标签项和网格。但我不确定如何为销售订单配置“属性”属性字段。我假设我可以捎带客户的“属性”定义?

我刚刚做的:

public class SOOrderExt : PXCacheExtension<PX.Objects.SO.SOOrder>
{
  #region Attributes

[CRAttributesField(typeof (Customer.customerClassID))]
public virtual string[] Attributes { get; set; }
  #endregion
}

答案视图:

namespace PX.Objects.SO
{
  public class SOOrderEntry_Extension:PXGraphExtension<SOOrderEntry>
  {
    [PXViewName("Answers")]
    public CRAttributeList<Customer>Answers;
  }
}

它显示了客户的属性...太棒了!但是需要根据销售订单保存它们。如果下线,客户的属性已经改变。首次提出订单时,销售订单应具有原始客户属性的副本。那么我该怎么做呢?谢谢!

【问题讨论】:

    标签: acumatica


    【解决方案1】:

    问题是属性的参考链接到客户,而不是您需要的订单(以保存属性)。为此,我们需要在CRAttributeList 类中编写我们自己的查询/关联调用。我创建了以下继承类,并且能够通过将 order ref noteid 与保存的答案相关联来使 Attributes 遵守订单。 CRAttributeList 类没有很好地覆盖,所以有很多复制的代码。您可以浏览源代码以查看完整的类并根据需要更新任何内容。我认为这可以简化,但现在它是一个可行的答案。

    按照您的方式保留您的订单 dac 扩展...

    public class SOOrderExt : PXCacheExtension<PX.Objects.SO.SOOrder>
    {
        [CRAttributesField(typeof (Customer.customerClassID))]
        public virtual string[] Attributes { get; set; }
    }
    

    替换视图以使用新类...

    [PXViewName("Answers")]
    public SalesCustomerAttributeList Answers;
    

    新类... (更改 SelectDelegate 和基础 SelectInternal)

    public class SalesCustomerAttributeList : CRAttributeList<Customer>
    {
        public SalesCustomerAttributeList(PXGraph graph) : base(graph)
        {
        }
    
        //Copy of private method from CRAttributeList
        protected string GetClassId(object row)
        {
            var classIdField = GetClassIdField(row);
            if (classIdField == null)
                return null;
    
            var entityCache = _Graph.Caches[row.GetType()];
    
            var classIdValue = entityCache.GetValueExt(row, classIdField.Name);
    
            return classIdValue?.ToString()?.Trim();
        }
    
        //Copy of private method from CRAttributeList
        protected Type GetClassIdField(object row)
        {
            if (row == null)
                return null;
    
    
            var fieldAttribute =
                _Graph.Caches[row.GetType()].GetAttributes(row, null)
                    .OfType<CRAttributesFieldAttribute>()
                    .FirstOrDefault();
    
            if (fieldAttribute == null)
                return null;
    
            return fieldAttribute.ClassIdField;
        }
    
        //Copy of private method from CRAttributeList
        protected Type GetEntityTypeFromAttribute(object row)
        {
            var classIdField = GetClassIdField(row);
            if (classIdField == null)
                return null;
    
            return classIdField.DeclaringType;
        }
    
        //Override to use desired query for sales order and customer/customer class related attributes
        protected override IEnumerable SelectDelegate()
        {
            return this.SelectInternal(
                (Customer)_Graph.Caches<Customer>()?.Current,
                (SOOrder)_Graph.Caches<SOOrder>()?.Current);
        }
    
        /// <summary>
        /// Find the customer default value based on the given answer
        /// </summary>
        protected bool TryGetCustomerAttributeValue(CSAnswers classAnswer, List<CSAnswers> customerAnswers, out string customerDefault)
        {
            customerDefault = null;
            if (classAnswer == null || customerAnswers == null)
            {
                return false;
            }
    
            foreach (var customerAttribute in customerAnswers)
            {
                if (customerAttribute.AttributeID != classAnswer.AttributeID)
                {
                    continue;
                }
                customerDefault = customerAttribute.Value;
                return true;
            }
    
            return false;
        }
    
        protected List<CSAnswers> GetCustomerAttributes(Customer customerRow)
        {
            return PXSelect<CSAnswers, Where<CSAnswers.refNoteID, Equal<Required<CSAnswers.refNoteID>>>>
                    .Select(_Graph, customerRow.NoteID).FirstTableItems.ToList();
        }
    
        //Override to use desired query for sales order and customer/customer class related attributes
        protected IEnumerable<CSAnswers> SelectInternal(Customer customerRow, SOOrder orderRow)
        {
            if (orderRow == null || customerRow == null)
            {
                yield break;
            }
    
            var noteId = GetNoteId(orderRow);
    
            if (!noteId.HasValue)
                yield break;
    
            var answerCache = _Graph.Caches[typeof(CSAnswers)];
            var orderCache = _Graph.Caches[orderRow.GetType()];
    
            List<CSAnswers> answerList;
    
            var status = orderCache.GetStatus(orderRow);
    
            if (status == PXEntryStatus.Inserted || status == PXEntryStatus.InsertedDeleted)
            {
                answerList = answerCache.Inserted.Cast<CSAnswers>().Where(x => x.RefNoteID == noteId).ToList();
            }
            else
            {
                answerList = PXSelect<CSAnswers, Where<CSAnswers.refNoteID, Equal<Required<CSAnswers.refNoteID>>>>
                    .Select(_Graph, noteId).FirstTableItems.ToList();
            }
    
            var classId = GetClassId(customerRow);
    
            CRAttribute.ClassAttributeList classAttributeList = new CRAttribute.ClassAttributeList();
            if (classId != null)
            {
                classAttributeList = CRAttribute.EntityAttributes(GetEntityTypeFromAttribute(customerRow), classId);
            }
    
            //when coming from Import scenarios there might be attributes which don't belong to entity's current attribute class or the entity might not have any attribute class at all
            if (_Graph.IsImport && PXView.SortColumns.Any() && PXView.Searches.Any())
            {
                var columnIndex = Array.FindIndex(PXView.SortColumns,
                    x => x.Equals(typeof(CSAnswers.attributeID).Name, StringComparison.OrdinalIgnoreCase));
    
                if (columnIndex >= 0 && columnIndex < PXView.Searches.Length)
                {
                    var searchValue = PXView.Searches[columnIndex];
    
                    if (searchValue != null)
                    {
                        //searchValue can be either AttributeId or Description
                        var attributeDefinition = CRAttribute.Attributes[searchValue.ToString()] ??
                                                CRAttribute.AttributesByDescr[searchValue.ToString()];
    
                        if (attributeDefinition == null)
                        {
                            throw new PXSetPropertyException(PX.Objects.CR.Messages.AttributeNotValid);
                        }
                        //avoid duplicates
    
                        if (classAttributeList[attributeDefinition.ToString()] == null)
                        {
                            classAttributeList.Add(new CRAttribute.AttributeExt(attributeDefinition, null, false, true));
                        }
                    }
                }
            }
    
            if (answerList.Count == 0 && classAttributeList.Count == 0)
            {
                yield break;
            }
    
            //attribute identifiers that are contained in CSAnswers cache/table but not in class attribute list
            List<string> attributeIdListAnswers =
                answerList.Select(x => x.AttributeID)
                    .Except(classAttributeList.Select(x => x.ID))
                    .Distinct()
                    .ToList();
    
            //attribute identifiers that are contained in class attribute list but not in CSAnswers cache/table
            List<string> attributeIdListClass =
                classAttributeList.Select(x => x.ID)
                    .Except(answerList.Select(x => x.AttributeID))
                    .ToList();
    
            //attribute identifiers which belong to both lists
            List<string> attributeIdListIntersection =
                classAttributeList.Select(x => x.ID)
                    .Intersect(answerList.Select(x => x.AttributeID))
                    .Distinct()
                    .ToList();
    
    
            var cacheIsDirty = answerCache.IsDirty;
    
            List<CSAnswers> output = new List<CSAnswers>();
    
            //attributes contained only in CSAnswers cache/table should be added "as is"
            output.AddRange(answerList.Where(x => attributeIdListAnswers.Contains(x.AttributeID)));
    
            var customerAnswers = GetCustomerAttributes(customerRow);
    
            //attributes contained only in class attribute list should be created and initialized with default value
            foreach (var attributeId in attributeIdListClass)
            {
                var classAttributeDefinition = classAttributeList[attributeId];
    
                if (PXSiteMap.IsPortal && classAttributeDefinition.IsInternal)
                    continue;
    
                if (!classAttributeDefinition.IsActive)
                    continue;
    
                CSAnswers answer = (CSAnswers)answerCache.CreateInstance();
                answer.AttributeID = classAttributeDefinition.ID;
                answer.RefNoteID = noteId;
                answer.Value = GetDefaultAnswerValue(classAttributeDefinition);
                if (TryGetCustomerAttributeValue(answer, customerAnswers, out var customerValue))
                {
                    answer.Value = customerValue;
                }
    
                if (classAttributeDefinition.ControlType == CSAttribute.CheckBox)
                {
                    bool value;
                    if (bool.TryParse(answer.Value, out value))
                        answer.Value = Convert.ToInt32(value).ToString(CultureInfo.InvariantCulture);
                    else if (answer.Value == null)
                        answer.Value = 0.ToString();
                }
    
                answer.IsRequired = classAttributeDefinition.Required;
                answer = (CSAnswers)(answerCache.Insert(answer) ?? answerCache.Locate(answer));
                output.Add(answer);
            }
    
            //attributes belonging to both lists should be selected from CSAnswers cache/table with and additional IsRequired check against class definition
            foreach (CSAnswers answer in answerList.Where(x => attributeIdListIntersection.Contains(x.AttributeID)).ToList())
            {
                var classAttributeDefinition = classAttributeList[answer.AttributeID];
    
                if (PXSiteMap.IsPortal && classAttributeDefinition.IsInternal)
                    continue;
    
                if (!classAttributeDefinition.IsActive)
                    continue;
    
                if (answer.Value == null && classAttributeDefinition.ControlType == CSAttribute.CheckBox)
                    answer.Value = bool.FalseString;
    
                if (answer.IsRequired == null || classAttributeDefinition.Required != answer.IsRequired)
                {
                    answer.IsRequired = classAttributeDefinition.Required;
    
                    var fieldState = View.Cache.GetValueExt<CSAnswers.isRequired>(answer) as PXFieldState;
                    var fieldValue = fieldState != null && ((bool?)fieldState.Value).GetValueOrDefault();
    
                    answer.IsRequired = classAttributeDefinition.Required || fieldValue;
                }
    
                output.Add(answer);
            }
    
            answerCache.IsDirty = cacheIsDirty;
    
            output =
                output.OrderBy(
                    x =>
                        classAttributeList.Contains(x.AttributeID)
                            ? classAttributeList.IndexOf(x.AttributeID)
                            : (x.Order ?? 0))
                    .ThenBy(x => x.AttributeID)
                    .ToList();
    
            short attributeOrder = 0;
    
            foreach (CSAnswers answer in output)
            {
                answer.Order = attributeOrder++;
                yield return answer;
            }
        }
    }
    

    有关属性选项卡页面条目的示例,您可以查看客户页面 - 属性选项卡。我复制了这个标签来测试这个答案。

    【讨论】:

    • 谢谢@Brendan。我已经复制了您的代码,但它没有默认销售订单上的属性。网格显示零记录。不应该是这个位来返回客户的默认空属性吗? if (status == PXEntryStatus.Inserted || status == PXEntryStatus.InsertedDeleted) { answerList = answerCache.Inserted.Cast().Where(x => x.RefNoteID == noteId).ToList(); }
    • 在尝试这个之前我没有设置客户类。因此,为了让我的工作,我将属性添加到客户类,然后去找该类的客户并输入属性值。然后从那里填写销售订单。您的客户是否定义了属性?
    • 我想我明白你在说什么了。让我回顾一下
    • 更新示例。我更改了 SalesCustomerAttributeList.SelectInternal 调用,以查看针对客户属性值插入的答案。如果客户包含该属性,那么它将使用该值。希望这会有所帮助。
    • 它以某种方式工作。感谢那。确实注意到,如果客户在提出订单时没有分配属性。然后将这些属性分配给客户。当您重新加载销售订单时。它的属性显示客户的最新属性。销售订单属性应该为空,因为当时客户属性也是空的。我认为它应该使用空值或空值保存销售订单属性。你觉得呢?
    猜你喜欢
    • 2021-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多