I have created 2 custom fields(UsrFFA and UsrFreeFreightDay) as below in the Customers Screen.Customers Screen
Then I created similar fields on the Sales Order Screen as below
Sales Order
I want these fields on Sales Order screen to populate values for respective Customer ID.
I went through the training material T200 and found this code
protected void SOOrder_CustomerID_FieldUpdated(PXCache sender,PXFieldUpdatedEventArgs e)
{
SOOrder order = e.Row as SOOrder;
BAccount customer =
PXSelectorAttribute.Select<SOOrder.customerID>(sender, order)
as BAccount;
if (customer != null)
{
Contact defContact = PXSelect<Contact,
Where<Contact.bAccountID, Equal<Required<Contact.bAccountID>>,
And<Contact.contactID, Equal<Required<Contact.contactID>>>>>
.Select(Base, customer.BAccountID, customer.DefContactID);
if (defContact != null)
{
ContactExt contactExt = PXCache<Contact>
.GetExtension<ContactExt>(defContact);
sender.SetValue<SOOrderExt.usrCRVerified>(order,
contactExt.UsrCreditRecordVerified);
}
}
}
I am not able to understand this code and how should I use it in my customization.
You should subscribe to FieldUpdated handler for the SOOrder.CustomerID field and populate your custom fields on the Sales Order screen in the same way as shown in Example 5.2: Inserting a Default Detail Data Record of the T200 training class:
protected virtual void ShipmentLine_ProductID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
// Obtain the new data record that contains the updated
// values of all data fields
ShipmentLine line = (ShipmentLine)e.Row;
line.Description = string.Empty;
if (line.ProductID != null)
{
Product product = PXSelectorAttribute.Select<ShipmentLine.productID>(sender, line) as Product;
if (product != null)
{
// Copy the product name to the description of the shipment line
line.Description = product.ProductName;
}
}
}
You might also check Step 3.1: Adding the FieldUpdated Event Handler (CustomerMaint) and Step 5.2: Customizing Business Logic for the Sales Orders form (SOOrderEntry) from the T300 training class for additional samples.
The code snippet below should accomplish desired results on the Sales Orders screen. If you still have issues understanding the code below, I highly recommend you to go through the T300 training class for very detailed hands-on exercises with step-by-step instructions.
public class SOOrderEntryExt : PXGraphExtension<SOOrderEntry>
{
public void SOOrder_CustomerID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
var order = e.Row as SOOrder;
if (order.CustomerID != null)
{
var customer = PXSelectorAttribute.Select<SOOrder.customerID>(sender, order) as BAccountR;
if (customer != null)
{
var customerExt = customer.GetExtension<BAccountExt>();
var orderExt = order.GetExtension<SOOrderExt>();
orderExt.UsrFFA = customerExt.UsrFFA;
orderExt.UsrFreeFreightDay = customerExt.UsrFreeFreightDay;
}
}
}
}
Customize the attributes on the SOOrder field in the following way:
For UsrFFA field
[PXDBString(100)]
[PXUIField(DisplayName="FFA", Visible = true, Enabled = false)]
[PXFormula(typeof(Selector<SOOrder.customerID, BAccountExt.usrFFA>))]
For UsrFreeFreightDay field
[PXDBString(100)]
[PXUIField(DisplayName="Free Freight Day", Visible = true, Enabled = false)]
[PXFormula(typeof(Selector<SOOrder.customerID, BAccountExt.usrFreeFreightDay>))]
Related
I need to get sitecore information that collected from anonymous users to give him availability to export it or opt out - [GDPR]
any idea about contact ID for anonymous !
The way of doing it is dependent on the sitecore version.
Sitcore 9 you can use right to be forgotten
Sitecore 8+ you have to implement the feature from scratch.
Regarding Anonymous user - If the user is really anonymous, then you done need to worry about the GDPR (my view). But sometimes we map user email and sensitive personal info to anonymous user by using forms or WFFM. You can use email address of that user to query xDB (Contact Identifiers) to get the contact and contactID. Then reset informations.
Also: please note that based on WFFFM save action config, anonymous user will store in Core DB and Contact List.
To forget a user, you can use the following code. It will execute the ExecuteRightToBeForgotten function on the contact and scrub their data.
Forget User
public bool ForgetUser()
{
var id = _contactIdentificationRepository.GetContactId();
if (id == null)
{
return false;
}
var contactReference = new IdentifiedContactReference(id.Source, id.Identifier);
using (var client = _contactIdentificationRepository.CreateContext())
{
var contact = client.Get(contactReference, new ContactExpandOptions());
if (contact != null)
{
client.ExecuteRightToBeForgotten(contact);
client.Submit();
}
}
return false;
}
Fake up some data
public void FakeUserInfo()
{
var contactReference = _contactIdentificationRepository.GetContactReference();
using (var client = SitecoreXConnectClientConfiguration.GetClient())
{
// we can have 1 to many facets
// PersonalInformation.DefaultFacetKey
// EmailAddressList.DefaultFacetKey
// Avatar.DefaultFacetKey
// PhoneNumberList.DefaultFacetKey
// AddressList.DefaultFacetKey
// plus custom ones
var facets = new List<string> { PersonalInformation.DefaultFacetKey };
// get the contact
var contact = client.Get(contactReference, new ContactExpandOptions(facets.ToArray()));
// pull the facet from the contact (if it exists)
var facet = contact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey);
// if it exists, change it, else make a new one
if (facet != null)
{
facet.FirstName = $"Myrtle-{DateTime.Now.Date.ToString(CultureInfo.InvariantCulture)}";
facet.LastName = $"McSitecore-{DateTime.Now.Date.ToString(CultureInfo.InvariantCulture)}";
// set the facet on the client connection
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, facet);
}
else
{
// make a new one
var personalInfoFacet = new PersonalInformation()
{
FirstName = "Myrtle",
LastName = "McSitecore"
};
// set the facet on the client connection
client.SetFacet(contact, PersonalInformation.DefaultFacetKey, personalInfoFacet);
}
if (contact != null)
{
// submit the changes to xConnect
client.Submit();
// reset the contact
_contactIdentificationRepository.Manager.RemoveFromSession(Analytics.Tracker.Current.Contact.ContactId);
Analytics.Tracker.Current.Session.Contact = _contactIdentificationRepository.Manager.LoadContact(Analytics.Tracker.Current.Contact.ContactId);
}
}
}
ContactIdentificationRepository
using System.Linq;
using Sitecore.Analytics;
using Sitecore.Analytics.Model;
using Sitecore.Analytics.Tracking;
using Sitecore.Configuration;
using Sitecore.XConnect;
using Sitecore.XConnect.Client.Configuration;
namespace Sitecore.Foundation.Accounts.Repositories
{
public class ContactIdentificationRepository
{
private readonly ContactManager contactManager;
public ContactManager Manager => contactManager;
public ContactIdentificationRepository()
{
contactManager = Factory.CreateObject("tracking/contactManager", true) as ContactManager;
}
public IdentifiedContactReference GetContactReference()
{
// get the contact id from the current contact
var id = GetContactId();
// if the contact is new or has no identifiers
var anon = Tracker.Current.Contact.IsNew || Tracker.Current.Contact.Identifiers.Count == 0;
// if the user is anon, get the xD.Tracker identifier, else get the one we found
return anon
? new IdentifiedContactReference(Sitecore.Analytics.XConnect.DataAccess.Constants.IdentifierSource, Tracker.Current.Contact.ContactId.ToString("N"))
: new IdentifiedContactReference(id.Source, id.Identifier);
}
public Analytics.Model.Entities.ContactIdentifier GetContactId()
{
if (Tracker.Current?.Contact == null)
{
return null;
}
if (Tracker.Current.Contact.IsNew)
{
// write the contact to xConnect so we can work with it
this.SaveContact();
}
return Tracker.Current.Contact.Identifiers.FirstOrDefault();
}
public void SaveContact()
{
// we need the contract to be saved to xConnect. It is only in session now
Tracker.Current.Contact.ContactSaveMode = ContactSaveMode.AlwaysSave;
this.contactManager.SaveContactToCollectionDb(Tracker.Current.Contact);
}
public IXdbContext CreateContext()
{
return SitecoreXConnectClientConfiguration.GetClient();
}
}
}
There is a custom field, that I declared for the Customer DAC:
public class CustomerExt : PXCacheExtension<Customer>
{
#region UsrDemoField
[PXDBString(255)]
[PXUIField(DisplayName = "Demo Field")]
public virtual string UsrDemoField { get; set; }
public abstract class usrDemoField : IBqlField { }
#endregion
}
Attempts to modify the ARInvoice Customer selector with the Customize Selector Columns popup didn't seem to work. How can I add my custom field into the ARInvoice customer selector?
Be aware, since Acumatica ERP build #17.201.0043, it's possible to customize the list of columns defined for AR Invoices' Customer lookup via the Customize Selector Columns dialog (available in the Data Class section of the Customization Manager). For step-by-step instructions please check the screenshot below:
To modify AR Invoices' Customer lookup on Acumatica ERP ver. 6.1 and earlier, please follow the steps below:
The definition of PXCustomizeSelectorColumns generated by the Customize Selector Columns popup brilliantly works with the majority of selectors inside Acumatica ERP. Basically, PXCustomizeSelectorColumns simply replaces originally defined columns for a selector with the custom set of columns during an initialization of PXCache:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public class PXCustomizeSelectorColumns: PXEventSubscriberAttribute
{
private readonly Type[] _columns;
public PXCustomizeSelectorColumns(params Type[] columns)
{
_columns = columns;
}
public override void CacheAttached(PXCache cache)
{
cache.SetAltered(this.FieldName, true);
foreach (PXEventSubscriberAttribute attr in cache.GetAttributes(null, this.FieldName))
{
PXSelectorAttribute sel = attr as PXSelectorAttribute;
if (sel == null)
continue;
sel.SetFieldList(_columns);
sel.Headers = null;
}
}
}
So what can cause the PXCustomizeSelectorColumns attribute to fail and not replace selector's originally defined columns? Any time the SetColumns method is executed on an instance of PXDimensionSelectorAttribute or PXSelectorAttribute after PXCache was initialized, there is no chance for PXCustomizeSelectorColumns to do its job.
[PXDBInt()]
[PXUIField(DisplayName = "Customer", Visibility = PXUIVisibility.Visible)]
[Serializable]
public class CustomerAttribute : AcctSubAttribute
{
...
public virtual void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (this.AttributeLevel == PXAttributeLevel.Item || e.IsAltered)
{
PopulateFields(sender);
}
PXFieldSelecting handler = GetAttribute<PXDimensionSelectorAttribute>().FieldSelecting;
handler(sender, e);
}
protected virtual void PopulateFields(PXCache sender)
{
if (_FieldList == null)
{
_FieldList = new string[this._fields.Length];
_HeaderList = new string[this._fields.Length];
for (int i = 0; i < this._fields.Length; i++)
{
Type cacheType = BqlCommand.GetItemType(_fields[i]);
PXCache cache = sender.Graph.Caches[cacheType];
if (cacheType.IsAssignableFrom(typeof(BAccountR)) ||
_fields[i].Name == typeof(BAccountR.acctCD).Name ||
_fields[i].Name == typeof(BAccountR.acctName).Name)
{
_FieldList[i] = _fields[i].Name;
}
else
{
_FieldList[i] = cacheType.Name + "__" + _fields[i].Name;
}
_HeaderList[i] = PXUIFieldAttribute.GetDisplayName(cache, _fields[i].Name);
}
}
var attr = GetAttribute<PXDimensionSelectorAttribute>().GetAttribute<PXSelectorAttribute>();
attr.SetColumns(_FieldList, _HeaderList);
}
...
}
With that said, to add a custom field into the ARInvoice Customer selector, one should replace all attributes declared for the ARInvoice.CustomerID field and redefine columns for the Customer selector within the CustomerActive attribute:
[PXDefault()]
[CustomerActive(typeof(Search<BAccountR.bAccountID>),
new Type[]
{
typeof(BAccountR.acctCD),
typeof(BAccountR.acctName),
typeof(CustomerExt.usrDemoField),
typeof(Address.addressLine1),
typeof(Address.addressLine2),
typeof(Address.postalCode),
typeof(CustomerAttribute.Contact.phone1),
typeof(Address.city),
typeof(Address.countryID),
typeof(CustomerAttribute.Location.taxRegistrationID),
typeof(Customer.curyID),
typeof(CustomerAttribute.Contact.salutation),
typeof(Customer.customerClassID),
typeof(Customer.status)
},
Visibility = PXUIVisibility.SelectorVisible, DescriptionField = typeof(Customer.acctName), Filterable = true, TabOrder = 2)]
After publishing the customization, custom Demo Field should finally appear in the ARInvoice Customer selector:
To enable searching against a custom field inside the ARInvoice Customer selector, open Invoices and Memos screen in the Layout Editor and type UsrDemoField as the GridProperties.FastFilterFields property of the Customer selector:
In C++ we can use QAbstractItemModel::setData() to modify the model's data. Searching the internet I only found how to read data from model to display it in a delegate. There are also some examples of adding and removing rows, but I could not find how to change the data of specific model index. Something like:
Slider {
onValueChanged: myModel.setData(0, {amount: value})
}
How can I modify data in a model from in QML?
In my projects I follow a different way to read/write data from/to QML models
I simply create two .qml file one to display and the another is a helper file to do database operations, and I create a model that inherits QAbstractItemModel and add four functions to it:
MyCustomObject * at(int index); // get an item to display
void reload(); // to notify QML view on update/delete
bool insert(MyCustomObject *p_myCustomObject); // insert an item in model or database
bool update(MyCustomObject *p_myCustomObject);// update an item to model or database
bool doDelete(int myCustomObjectID);// delete an item from model or database
then I create a local object to read/write, and when displaying these data I populate the local object value from the model and when I want to save I write that object to database
add this property to your main display class
property MyCustomObject myCustomObject : MyCustomObject{} // to read/write UI value ti/from it
and here is a helper class that reads UI values and insert, update or delete to/from models
Note: this class is for one of my applications, though just read it and modify it to meet your needs
import QtQuick 2.0
import DOO.Commands.Son 1.0
import DOOTypes 1.0
QtObject {
// read ui values into local son
function readUIValues()
{
var v_son = SonFactory.createObject()
v_son.name = sonName.text
v_son.image = sonImage.picture
v_son.age = sonAge.text
v_son.entryDate = Date.fromLocaleDateString(Qt.locale(), sonEntryDate.text, "dd-MM-yyyy")
v_son.commingFrom = sonCommingFrom.text
v_son.disabilityKind.kind = sonDisabilityKind.currentIndex
v_son.caseDescription = sonCaseDescription.text
return v_son
}
// simple UI validation
function validateUIValues()
{
if(sonName.text == "") return false
if(sonImage.picture == "") return false
if(sonAge.text < 1 || sonAge.text > 100) return false
if(Date.fromLocaleDateString(Qt.locale(), sonEntryDate.text, "dd-MM-yyyy") == "Invalid Date") return false
if(sonCommingFrom.text == "") return false
if(sonDisabilityKind.text == "") return false
if(sonCaseDescription.text == "") return false
return true
}
// save or update a son into database
function save()
{
if (!validateUIValues())
{
dooNotifier.showMessage("خطأ","ليدك مدخلات غير صحيحة، يُرجى التأكد من إدخال قيم صحيحة")
return
}
var v_son = readUIValues()
if(disMode === DOO.CreateNew)
{
if(SonResultsModel.insert(v_son))
{
dooNotifier.showMessage("تم بنجاح","تم إضافة الابن بنجاح")
sonDisplay.hide()
}
else
{
dooNotifier.showMessage("فشل","فشل إضافة الابن")
DOOLogger.log(SonResultsModel.lasrErrorText())
}
}
else
{
//get the ID of the son bieng edited
v_son.sonID = son.sonID
if(SonResultsModel.update(v_son))
{
dooNotifier.showMessage("تم بنجاح","تم تحديث الابن بنجاح")
sonDisplay.hide()
}
else
{
dooNotifier.showMessage("فشل","فشل تحديث الابن")
DOOLogger.log(SonResultsModel.lasrErrorText())
}
}
v_son.destroy()
}
function doDelete(sonID)
{
if(SonResultsModel.doDelete(sonID)) {
dooNotifier.showMessage("تم بنجاح","تم حذف الابن بنجاح")
sonDisplay.hide()
}
else dooNotifier.showMessage("فشل","فشل حذف الابن")
}
}
this is the way I use to read and write to/from databases I hope it helps
Our client wants to automatically publish related media items when publishing a page. They're not using workflow which would have made things simpler, so I need to find another way. At the moment I've created a custom publish pipeline processor (as shown in this blog post) where I've enabled History storage for the web database and get the list of changed items from there. When looping through the changed items I'm checking for any related media items and publish them.
This works fine, but I just wanted to check if there's any pitfalls to watch out for or if there is a better way of doing this. Anyone have any ideas?
The best way without using workflow is to replace the AddItemReferences processor in the PublishItem workflow. There you can add what types of items will be published along with the original item.
Here is a blog post Alex Shyba about it.
Here is my local implementation
public class AddItemReferences : Sitecore.Publishing.Pipelines.PublishItem.AddItemReferences
{
private readonly static ILogger _logger = AppLogger.GetNamedLogger(typeof(AddItemReferences));
protected override List<Item> GetItemReferences(PublishItemContext context)
{
Assert.ArgumentNotNull(context, "context");
var list = new List<Item>();
// calling base method which processes links from FileDropArea field
list.AddRange(base.GetItemReferences(context));
// adding our "own" related items
list.AddRange(GetRelatedReferences(context));
return list;
}
protected virtual List<Item> GetRelatedReferences(PublishItemContext context)
{
Assert.ArgumentNotNull(context, "context");
var relatedReferenceList = new List<Item>();
if (context.PublishOptions.Mode == PublishMode.SingleItem )
{
try
{
var sourceItem = context.PublishHelper.GetSourceItem(context.ItemId);
if (sourceItem.Paths.IsContentItem)
{
var itemLinks = sourceItem.Links.GetValidLinks();
ItemLink[] referers = Globals.LinkDatabase.GetReferers(sourceItem);
relatedReferenceList.AddRange(GetMediaItems(itemLinks));
relatedReferenceList.AddRange(GetAliases(referers));
}
}
catch (Exception ex)
{
var options = context.PublishOptions;
StringBuilder msg = new StringBuilder();
msg.AppendLine("Publishing options");
msg.AppendLine("Deep: " + options.Deep);
msg.AppendLine("From date: " + options.FromDate);
msg.AppendLine("Language: " + options.Language);
msg.AppendLine("Mode: " + options.Mode);
msg.AppendLine("PublishDate: " + options.PublishDate);
msg.AppendLine("Targets: " + string.Join(",",options.PublishingTargets.ToArray()));
msg.AppendLine("Republish all: " + options.RepublishAll);
msg.AppendLine("Root item: " + options.RootItem);
msg.AppendLine("Source database: " + options.SourceDatabase.Name);
_logger.LogError(msg.ToString(), ex);
}
}
return relatedReferenceList;
}
private static IEnumerable<Item> GetMediaItems(ItemLink[] itemLinks)
{
foreach (var link in itemLinks)
{
var item = link.GetTargetItem();
if (item == null)
continue;
if (item.Paths.IsMediaItem)
{
yield return item;
}
}
}
private static IEnumerable<Item> GetAliases(ItemLink[] referrers)
{
foreach (var link in referrers)
{
var item = link.GetSourceItem();
if (item != null && IsAlias(item))
yield return item;
}
}
private static bool IsAlias(Item item)
{
return item.TemplateID.Guid == DataAccessSettings.Templates.AliasTemplateId;
}
}
Input for risk areas:
Missing entries in History storage if editing session is above 30 days prior to publish
Finding related media items involves both link fields and also rich text fields, there can be possible direct links to media, these could be handled and transformed to correctly formatted links.
Alternative solutions
Depending on the Sitecore maturity of your editors another user model could be that you autopublish the media Items from the Save Pipeline. For some users this is easier to understand, since the publishing model is then restricted to handling page visibility.
I'm trying to reproduce the Activities page in Microsoft CRM 4.0 via web services. I can retrieve a list of activities, and I believe I need to use ActivityPointers to retrieve the entities but have so far been unsuccessful. Would I need to loop through every single entity returned from the first query to retrieve the ActivityPointer for it? And if so, how would I then get the "Regarding" field or Subject of the activity (eg: email).
The code to retrieve the activities is:
var svc = GetCrmService();
var cols = new ColumnSet();
cols.Attributes = new[] { "activityid", "addressused", "scheduledstart", "scheduledend", "partyid", "activitypartyid", "participationtypemask", "ownerid" };
var query = new QueryExpression();
query.EntityName = EntityName.activityparty.ToString();
query.ColumnSet = cols;
LinkEntity link = new LinkEntity();
//link.LinkCriteria = filter;
link.LinkFromEntityName = EntityName.activitypointer.ToString();
link.LinkFromAttributeName = "activityid";
link.LinkToEntityName = EntityName.activityparty.ToString();
link.LinkToAttributeName = "activityid";
query.LinkEntities = new[] {link};
var activities = svc.RetrieveMultiple(query);
var entities = new List<ICWebServices.activityparty>();
RetrieveMultipleResponse retrieved = (RetrieveMultipleResponse) svc.Execute(request);
//var pointers = new List<activitypointer>();
foreach (activityparty c in activities.BusinessEntities)
{
entities.Add(((activityparty)c));
//the entities don't seem to contain a link to the email which they came from
}
Not sure if I understand your problem, but the field "activityid" in the activitypointer object is the same activityid as the underlying activity (email, task, phonecall, etc). The regardingobjectid is the link to the regarding entity.
Heres what you need to get the equivalent of the Activities page
ColumnSet cols = new ColumnSet()
{
Attributes = new string[] { "subject", "regardingobjectid", "regardingobjectidname", "regardingobjectidtypecode", "activitytypecodename", "createdon", "scheduledstart", "scheduledend" }
};
ConditionExpression condition = new ConditionExpression()
{
AttributeName = "ownerid",
Operator = ConditionOperator.Equal,
Values = new object[] { CurrentUser.systemuserid.Value } //CurrentUser is an systemuser object that represents the current user (WhoAmIRequest)
};
FilterExpression filter = new FilterExpression()
{
Conditions = new ConditionExpression[] { condition },
FilterOperator = LogicalOperator.And
};
QueryExpression query = new QueryExpression()
{
EntityName = EntityName.activitypointer.ToString(),
ColumnSet = cols,
Criteria = filter
};
BusinessEntityCollection activities = svc.RetrieveMultiple(query);
foreach (activitypointer activity in activities)
{
//do something with the activity
//or get the email object
email originalEmail = (email)svc.Retrieve(EntityName.email.ToString(), activity.activityid.Value, new AllColumns());
}