I'm trying to build a Workflow that add a Contact to a marketing list.
Everything seems to be fine, but when the code finishes firing, and I go to the marketing list -> members the contact is not in the list.
public class ContactToMList : CodeActivity
{
[Input("Contatto")]
[ReferenceTarget("contact")]
public InArgument<EntityReference> contact { get; set; }
[Input("Marketing List")]
[ReferenceTarget("list")]
public InArgument<EntityReference> MList { get; set; }
[Input("Inserimento")]
public InArgument<bool> inserimento { get; set; }
bool action = false;
private static IOrganizationService myService = null;
private static Log_Entity log = new Log_Entity(string.Empty, myService);
protected override void Execute(CodeActivityContext executionContext)
{
try
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
// Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
// Create the Organiztion service
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
myService = service;
log.WriteLog("");
// Get the target entity from the context
Entity target = (Entity)context.InputParameters["Target"];
Guid contactiId = contact.Get<EntityReference>(executionContext).Id;
Guid ListId = MList.Get<EntityReference>(executionContext).Id;
bool insert = inserimento.Get<bool>(executionContext);
// Prepare DataContext by using AutoGenerated cs file
XrmDataContext datacontext = new XrmDataContext(service);
var MyContact = (from c in datacontext.ContactSet where c.ContactId == contactiId select c.Id).ToArray();
var MyList = (from l in datacontext.ListSet where l.Id == ListId select l).ToList().FirstOrDefault();
// tutti i membri della lista di marketing
var members = (from m in datacontext.ListMemberSet where m.ListId.Id == MyList.ListId select m.EntityId.Id).ToArray();
foreach (Guid id in members)
if (MyContact.FirstOrDefault() == id)
action = true;
if (insert && !action)
{
AddListMembersListRequest AddMemberRequest = new AddListMembersListRequest();
AddMemberRequest.ListId = ListId;
AddMemberRequest.MemberIds = MyContact;
// Use AddListMembersListReponse to get information about the request execution
AddListMembersListResponse AddMemberResponse = service.Execute(AddMemberRequest) as AddListMembersListResponse;
//service.Update(MyList);
}
else if (!insert && action)
{
RemoveMemberListRequest RemoveMemberRequest = new RemoveMemberListRequest();
RemoveMemberRequest.ListId = ListId;
RemoveMemberRequest.EntityId = MyContact.FirstOrDefault();
// Use AddListMembersListReponse to get information about the request execution
RemoveMemberListResponse RemoveMemberResponse = service.Execute(RemoveMemberRequest) as RemoveMemberListResponse;
// service.Update(MyList);
}
}
catch (Exception ex)
{
log.WriteLog(ex.Message);
}
}
}
aren't you erasing your values for AddMemberRequest.MemberIds after you set it?
EDIT:
Ok, I think I found it this time. Your public InArgument<bool> inserimento { get; set; } is likely the culprit.
In this case, your Workflow activity expects this to be defined upstream of the call to this Workflow. It's very likely statically set and never changed for both the Insert and Remove instances. If this is true, then it's being essentially hard coded for the Insert case, which makes the else if (!insert && action) evaluate to True for Remove and the if (insert && !action) evaluate to False for the Insert.
Since the code does work for Remove, it's reasonable to assume the bool action is working; therefore, I would start by looking into the other bool variable.
Let me know if I've missed it. (or if I'm right, I wouldn't mind the green check mark.)
Related
The goal is taking the Journal Transaction generated from the AP Bill page and adding 2 additional rows in GLTran.
1st Attempt
First, I extended the Release action from the Journal Transactions graph to include the 2 new lines:
public class JournalEntryExt : PXGraphExtension<JournalEntry>
{
public delegate IEnumerable ReleaseDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable Release(PXAdapter adapter, ReleaseDelegate baseMethod)
{
baseMethod(adapter);
//new code
GLTran tranRow = new GLTran();
tranRow = this.Base.GLTranModuleBatNbr.Insert(tranRow);
tranRow.AccountID = 2713;
tranRow.SubID = 467;
tranRow.CuryDebitAmt = 100;
this.Base.GLTranModuleBatNbr.Update(tranRow);
tranRow = new GLTran();
tranRow = this.Base.GLTranModuleBatNbr.Insert(tranRow);
tranRow.AccountID = 1514;
tranRow.SubID = 467;
tranRow.CuryCreditAmt = 100;
this.Base.GLTranModuleBatNbr.Update(tranRow);
this.Base.Actions.PressSave();
return adapter.Get();
}
Result: Creating and releasing the batch, entered the 2 new lines correctly.
After this, I thought that releasing the AP Bill would also trigger this extended logic from the GL Page. However, that didn't occur - The release of the Bill doesn't seem to re-use the Release logic defined in the GL page.
2nd Attempt
Then, I went back to the GL page and included the logic in the RowPersisted event, so that the 2 new lines would get created right after saving the document:
public class JournalEntryExt : PXGraphExtension<JournalEntry>
{
protected virtual void Batch_RowPersisted(PXCache sender, PXRowPersistedEventArgs e)
{
if (e.Row == null)
{
return;
}
Batch batchRow = (Batch)e.Row;
if (batchRow != null
&& e.Operation == PXDBOperation.Insert
&& e.TranStatus == PXTranStatus.Completed)
{
////new code
GLTran tranRow = new GLTran();
tranRow = this.Base.GLTranModuleBatNbr.Insert(tranRow);
tranRow.AccountID = 2713;
tranRow.SubID = 467;
tranRow.CuryDebitAmt = 102;
this.Base.GLTranModuleBatNbr.Update(tranRow);
tranRow = new GLTran();
tranRow = this.Base.GLTranModuleBatNbr.Insert(tranRow);
tranRow.AccountID = 1514;
tranRow.SubID = 467;
tranRow.CuryCreditAmt = 102;
this.Base.GLTranModuleBatNbr.Update(tranRow);
}
}
Result: Creating and saving the Batch correctly entered the 2 new lines.
After this, I thought that releasing the AP Bill would trigger this extended event, given that a Journal Entry graph should get created and used from the Bill page, but in this case, also releasing the AP Bill, did not add the 2 new lines in the generated Batch.
3rd Attempt
Then I thought I could extend the Bill's Release action and take control of the generated Journal Entry with the Search<> method. However, in this case, the extended logic seems to be executed within a transaction as the Document.Current.BatchNbr was still NULL:
4th Attempt
Finally, I tried to extend the Persist() method of APReleaseProcess similarly to how it's done in the guide T300, however none of the methods are listed (version 17.207.0029):
Any other ideas as to how to enter these GL Lines?
Thanks!
Hopefully, it didn't take you forever to go through these 4 attempts... I've got to say though, the number of efforts and details in your question is quite impressive and definitely very appreciated!
A 2-step customization will be required to insert 2 additional GL Transactions in the Batch generated for an AP Bill:
to insert additional GL Transactions, you need to override Persist method within the JournalEntry BLC extension and invoke the logic to insert additional GLTrans only if the custom ModifyBatchFromAP boolean flag value equals True:
using PX.Data;
using System;
namespace PX.Objects.GL
{
public class JournalEntry_Extension : PXGraphExtension<JournalEntry>
{
private bool modifyBatchFromAP = false;
public bool ModifyBatchFromAP
{
get
{
return modifyBatchFromAP;
}
set
{
modifyBatchFromAP = value;
}
}
[PXOverride]
public void Persist(Action del)
{
if (ModifyBatchFromAP)
{
var glTran = Base.GLTranModuleBatNbr.Insert();
Base.GLTranModuleBatNbr.SetValueExt<GLTran.accountID>(glTran, "20000");
glTran = Base.GLTranModuleBatNbr.Update(glTran);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.subID>(glTran, "000000");
glTran.CuryDebitAmt = 100;
glTran.TranDesc = "Additional Debit Transaction for AP Doc";
Base.GLTranModuleBatNbr.Update(glTran);
glTran = Base.GLTranModuleBatNbr.Insert();
Base.GLTranModuleBatNbr.SetValueExt<GLTran.accountID>(glTran, "20200");
glTran = Base.GLTranModuleBatNbr.Update(glTran);
Base.GLTranModuleBatNbr.SetValueExt<GLTran.subID>(glTran, "000000");
glTran.CuryCreditAmt = 100;
glTran.TranDesc = "Additional Credit Transaction for AP Doc";
Base.GLTranModuleBatNbr.Update(glTran);
}
del();
}
}
}
after that in the overridden Release action within the APInvoiceEntry_Extension, you will subscribe to the InstanceCreated event for the JournalEntry BLC type to set ModifyBatchFromAP flag value to True allowing your logic from step 1 to execute for the Batch generated only for an AP document:
using PX.Data;
using PX.Objects.GL;
using System.Collections;
namespace PX.Objects.AP
{
public class APInvoiceEntry_Extension : PXGraphExtension<APInvoiceEntry>
{
public delegate IEnumerable ReleaseDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable Release(PXAdapter adapter, ReleaseDelegate baseMethod)
{
PXGraph.InstanceCreated.AddHandler<JournalEntry>((JournalEntry graph) =>
{
graph.GetExtension<JournalEntry_Extension>().ModifyBatchFromAP = true;
});
return baseMethod(adapter);
}
}
}
P.S. it is not currently possible to use the Select Method to Override dialog with the APReleaseProcess class due to PXHiddenAttribute applied to it. Let me forward this to our Engineering Team to bring their attention on that matter.
I created a custom Action on Contracts that updates the description as well as attributes.
It works fine when doing a single entry and pressing the action button but during Mass Action only the description is updated and saved the attributes are not.
What do I need to do to make the Attributes values save correctly during Mass Action?
public PXSelect<CSAnswers,
Where<CSAnswers.refNoteID, Equal<Current<Contract.noteID>>>> CSAttr;
protected IEnumerable testAction(PXAdapter adapter)
{
//get the activation parameters
var a = CSAttr.Select();
Contract mycontract = Base.CurrentContract.Select();
foreach (CSAnswers item in a.ToList())
{
if (item.AttributeID == "ACTA")
{
item.Value = "Won't Update1";
CSAttr.Update(item); //shouldn't this update?
}
else if (item.AttributeID == "ACTB") //desired mode set by user
{
item.Value = "Won't Update2";
CSAttr.Update(item); //shouldn't this update?
}
}
mycontract.Description = "This Works Fine";
Base.CurrentContract.Update(mycontract);
Base.Actions.PressSave();
return adapter.Get();
}
No need to declare custom CSAttr data view: ContractMaint already contains Answers data view to work with Attributes.
TestAction implemented as below, successfully updates both Contract Description and Attribute Value on a brand new 6.00.1596 Acumatica ERP instance when launched from GI set up as entry-point list:
public class ContractMaintExt : PXGraphExtension<ContractMaint>
{
public override void Initialize()
{
TestAction.IsMass = true;
}
public PXAction<Contract> TestAction;
[PXButton]
[PXUIField(DisplayName = "Test Action")]
public void testAction()
{
Contract mycontract = Base.CurrentContract.Select();
foreach (CSAnswers attr in Base.Answers.Select())
{
if (attr.AttributeID == "CONFIGURAB")
{
attr.Value = "Updated";
Base.Answers.Update(attr); //shouldn't this update?
}
}
mycontract.Description = "This Works Fine";
Base.Contracts.Update(mycontract);
Base.Actions.PressSave();
}
}
everyone,
I'm having a problem that does not appear in the output parameter list on the right side under "Look for" underneath "Local Values", I do not understand the problem or reason for not appear, since in terms of input parameters's okay.
protected override void Execute(CodeActivityContext executionContext)
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
//Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
tracingService.Trace("Creating Account");
Account entity = new Account();
entity.Name = AccountName.Get<string>(executionContext);
Guid entityId = service.Create(entity);
string a = entity.Name;
AccountNameTest.Set(executionContext, a);
tracingService.Trace("Account created with Id {0}", entityId.ToString());
tracingService.Trace("Create a task for the account");
Task newTask = new Task();
newTask.Subject = TaskSubject.Get<string>(executionContext);
newTask.RegardingObjectId = new EntityReference(Account.EntityLogicalName, entityId);
Guid taskId = service.Create(newTask);
tracingService.Trace("Task has been created");
tracingService.Trace("Retrieve the task using QueryByAttribute");
QueryByAttribute query = new QueryByAttribute();
query.Attributes.AddRange(new string[] { "regardingobjectid" });
query.ColumnSet = new ColumnSet(new string[] { "subject" });
query.EntityName = Task.EntityLogicalName;
query.Values.AddRange(new object[] { entityId });
tracingService.Trace("Executing the Query for entity {0}", query.EntityName);
//Execute using a request to test the OOB (XRM) message contracts
RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.Query = query;
Collection<Entity> entityList = ((RetrieveMultipleResponse)service.Execute(request)).EntityCollection.Entities;
//Execute a request from the CRM message assembly
tracingService.Trace("Executing a WhoAmIRequest");
service.Execute(new WhoAmIRequest());
if (1 != entityList.Count)
{
tracingService.Trace("The entity list was too long");
throw new InvalidPluginExecutionException("Query did not execute correctly");
}
else
{
tracingService.Trace("Casting the Task from RetrieveMultiple to strong type");
Task retrievedTask = (Task)entityList[0];
if (retrievedTask.ActivityId != taskId)
{
throw new InvalidPluginExecutionException("Incorrect task was retrieved");
}
tracingService.Trace("Retrieving the entity from IOrganizationService");
//Retrieve the task using Retrieve
retrievedTask = (Task)service.Retrieve(Task.EntityLogicalName, retrievedTask.Id, new ColumnSet("subject"));
if (!string.Equals(newTask.Subject, retrievedTask.Subject, StringComparison.Ordinal))
{
throw new InvalidPluginExecutionException("Task's subject did not get retrieved correctly");
}
//Update the task
retrievedTask.Subject = UpdatedTaskSubject.Get<string>(executionContext);
service.Update(retrievedTask);
}
}
//
[Input("Name conta")]
[Default("testv01")]
public InArgument<string> AccountName { get; set; }
[Input("Task")]
[Default("testv01")]
public InArgument<string> TaskSubject { get; set; }
[Input("Update task")]
[Default("testUPDATED:v01}")]
public InArgument<string> UpdatedTaskSubject { get; set; }
[Output("Account ID Guid")]
[Default("testUPDATED:v01")]
public OutArgument<string> AccountNameTest { get; set; }
Ok, problem solved, just restart IIS to assume the fields, or through version change. The problem was the update of the plugin, this also happens with workflows. According to CRM 4.0 i realized this situation does not happen in CRM 4.0.
Even though this Question is already answered I wanted to share two Cases where this solution didn't work (even in a recent Version of CRM):
Case 1
Having choosen Input-Parameter-Names that contained german Umlauts (äöüß).
IIS-restart did not help.
Choosing Names without Umlauts solved the Problem for me.
Case 2
We recently also had a case where a normal In-Argument did not show up, even after restarting the entire Maschine CRM was running on. The Solution was not to obvious:
Open PluginRegistrationTool from SDK
Choose the Assembly that contains your CWA
Choose your CWA
Hit the Save-Button in the Properties-Tab of your CWA
My question is related to this one, but instead of changing a question I thought it Would be better to ask a new one.
I've now got a list of IContent items using the _taxonomyService.GetContentItems(term)
as suggested by #Bertrand Le Roy in the question mentioned above
But how do I turn this into a useful Html string, that I can update on the client via an ajax post?
public class HomeController : Controller
{
private readonly IOrchardServices _services;
private readonly IBlogService _blogService;
private readonly IBlogPostService _blogPostService;
private readonly IFeedManager _feedManager;
private readonly IArchiveConstraint _archiveConstraint;
private readonly ITaxonomyService _taxonomyService;
public HomeController(
IOrchardServices services,
IBlogService blogService,
IBlogPostService blogPostService,
IFeedManager feedManager,
IShapeFactory shapeFactory,
IArchiveConstraint archiveConstraint,
ITaxonomyService taxonomyService) {
_services = services;
_blogService = blogService;
_blogPostService = blogPostService;
_feedManager = feedManager;
_archiveConstraint = archiveConstraint;
T = NullLocalizer.Instance;
Shape = shapeFactory;
_taxonomyService = taxonomyService;
}
dynamic Shape { get; set; }
public Localizer T { get; set; }
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult ListByArchive(string path, IEnumerable<string> category)
{
try
{
// get year and month from path
path = path.ToLower().Substring(path.LastIndexOf(#"/archive/", StringComparison.Ordinal) + 9);
var date = path.Split('/');
var month = int.Parse(date[1]);
var year = int.Parse(date[0]);
// get list of terms ids from strings
var taxonomyPart = _taxonomyService.GetTaxonomyByName("Category");
var terms = category.Select(cat => _taxonomyService.GetTermByName(taxonomyPart.Id, cat)).ToList();
// get list of content items by term avoiding duplicates
var posts = new List<IContent>();
foreach (var term in terms)
{
var items = _taxonomyService.GetContentItems(term);
foreach (var item in items)
{
if (!posts.Select(p => p.Id).Contains(item.Id))
{
posts.Add(item);
}
}
}
// filter by date
var byDate = posts.Where(x =>
{
var publishedUtc = x.ContentItem.As<CommonPart>().CreatedUtc;
return
publishedUtc != null
&& publishedUtc.Value.Month == month
&& publishedUtc.Value.Year == year;
});
....
This gets me my list of IContent, but how do I get a the html for the rendered list ?
I've tried
var range = byDate.Select(x => _services.ContentManager.BuildDisplay(x, "Summary"));
var list = Shape.List();
list.AddRange(range);
dynamic viewModel = Shape.ViewModel().ContentItems(list);
var html = View((object)viewModel);
return Json(new { html = html });
but it returns an empty view,
{"html":{"MasterName":"","Model":[],"TempData":[],"View":null,"ViewBag":{},"ViewData":[],"ViewEngineCollection":[{"HostContainer":{}}],"ViewName":""}}
I have a view called ListByArchive.cshtml, that matches the one it the orchard.blog module.
As an aside, I should be returning a partial view result, instead of a jason result, but when I change the Action result type I get a 404. result from the server.
This is never going to work the way you think it does:
var html = View((object)viewModel);
The easiest way to return HTML representing the content item is to:
Mark your action with ThemedAttribute, ie. [Themed(false)]
Return new ShapeResult(this, viewModel) (full view) or new ShapePartialResult(this, viewModel) (partial view) instead of Json(new { html = html })
Rendering a shape/view to string inside the action is also possible, but way more tricky.
EDIT: I assumed you already have /Views/ViewModel.cshtml file in place. Like Bertrand Le Roy noted below - if it's not there, you need to add one to be able to create a shape using Shape.ViewModel().
I have several tables in my database that have read-only fields that get set on Inserting and Updating, namely: AddDate (DateTime), AddUserName (string), LastModDate (DateTime), LastModUserName (string).
All of the tables that have these values have been set to inherit from the following interface:
public interface IUserTrackTable
{
string AddUserName { get; set; }
DateTime AddDate { get; set; }
string LastModUserName { get; set; }
DateTime LastModDate { get; set; }
}
As such, I have the following method on the Edit.aspx page:
protected void DetailsDataSource_Updating(object sender, LinqDataSourceUpdateEventArgs e)
{
IUserTrackTable newObject = e.NewObject as IUserTrackTable;
if (newObject != null)
{
newObject.LastModUserName = User.Identity.Name;
newObject.LastModDate = DateTime.Now;
}
}
However, by the time it hits this method, the e.OriginalObject has already lost the values for all four fields, so a ChangeConflictException gets thrown during the actual Update. I have tried adding the four column names to the DetailsView1.DataKeyNames array in the Init event handler:
protected void Page_Init(object sender, EventArgs e)
{
// other things happen before this
var readOnlyColumns = table.Columns.Where(c => c.Attributes.SingleOrDefaultOfType<ReadOnlyAttribute>(ReadOnlyAttribute.Default).IsReadOnly).Select(c => c.Name);
DetailsView1.DataKeyNames = DetailsView1.DataKeyNames.Union<string>(readOnlyColumns).ToArray<string>();
DetailsView1.RowsGenerator = new CustomFieldGenerator(table, PageTemplates.Edit, false);
// other things happen after this
}
I've tried making that code only happen on PostBack, and still nothing. I'm at a lose for how to get the values for all of the columns to make the round-trip.
The only thing the CustomFieldGenerator is handling the ReadOnlyAttribute, following the details on C# Bits.
UPDATE: After further investigation, the values make the round trip to the DetailsView_ItemUpdating event. All of the values are present in the e.OldValues dictionary. However, they are lost by the time it gets to the LinqDataSource_Updating event.
Obviously, there are the "solutions" of making those columns not participate in Concurrency Checks or other ways that involve hard-coding, but the ideal solution would dynamically add the appropriate information where needed so that this stays as a Dynamic solution.
i Drovani, I assume you want data auditing (see Steve Sheldon's A Method to Handle Audit Fields in LINQ to SQL), I would do this in the model in EF4 you can do it like this:
partial void OnContextCreated()
{
// Register the handler for the SavingChanges event.
this.SavingChanges += new EventHandler(context_SavingChanges);
}
private static void context_SavingChanges(object sender, EventArgs e)
{
// handle auditing
AuditingHelperUtility.ProcessAuditFields(objects.GetObjectStateEntries(EntityState.Added));
AuditingHelperUtility.ProcessAuditFields(objects.GetObjectStateEntries(EntityState.Modified), InsertMode: false);
}
internal static class AuditingHelperUtility
{
internal static void ProcessAuditFields(IEnumerable<Object> list, bool InsertMode = true)
{
foreach (var item in list)
{
IAuditable entity = item as IAuditable;
if (entity != null)
{
if (InsertMode)
{
entity.InsertedBy = GetUserId();
entity.InsertedOn = DateTime.Now;
}
entity.UpdatedBy = GetUserId();
entity.UpdatedOn = DateTime.Now;
}
}
}
}
Sadly this is not possible with EF v1