Copy User Fields from CRM Quote to Sales Order - acumatica

I am trying to copy some user fields from the CRM Quote to the Sales Order. The CRM Quote uses a different object than the Sales Quote and there doesn't appear to be a way to relate it back. I tried overriding the Create Sales Order to add a handler, but this didn't seem to work Any help would be appreciated. Here is the code I tried:
public class OpportunityMaint_Extension : PXGraphExtension<OpportunityMaint>
{
public delegate IEnumerable CreateSalesOrderDelegate(PXAdapter adapter);
[PXOverride]
public virtual IEnumerable CreateSalesOrder(PXAdapter adapter, CreateSalesOrderDelegate baseMethod)
{
Base.RowInserting.AddHandler<SOLine>((sender, e) =>
{
SOLine orderLine = e.Row as SOLine;
if (orderLine == null) return;
SOLineExt orderLineExt = orderLine.GetExtension<SOLineExt>();
var product = Base.Products.Current;
CROpportunityProductsExt productExt = product.GetExtension<CROpportunityProductsExt>();
orderLineExt.UsrHasAnticipatedDiscount = productExt.UsrHasAnticipatedDiscount;
orderLineExt.UsrAnticipatedDiscountPct = productExt.UsrAnticipatedDiscountPct;
orderLineExt.UsrAnticipatedDiscountAmt = productExt.UsrAnticipatedDiscountAmt;
orderLineExt.UsrAnticipatedUnitPrice = productExt.UsrAnticipatedUnitPrice;
orderLineExt.UsrTotalAnticipatedDiscountAmt = productExt.UsrTotalAnticipatedDiscountAmt;
});
return baseMethod(adapter);
}
}
Thanks!

There are two posts with answers to this same question:
Populate custom field while creating sale order from opportunity
How to pass custom field vales from Opportunity to sales Order?
To sum it up, you can add a rowinserting event handler within the button action or my preference is within DoCreateSalesOrder (extending OpportunityMaint) like the example below...
[PXOverride]
public virtual void DoCreateSalesOrder(OpportunityMaint.CreateSalesOrderFilter param, Action<OpportunityMaint.CreateSalesOrderFilter> del)
{
PXGraph.InstanceCreated.AddHandler<SOOrderEntry>(graph =>
{
graph.RowInserting.AddHandler<SOLine>((cache, args) =>
{
var soLine = (SOLine)args.Row;
if (soLine == null)
{
return;
}
CROpportunityProducts opProduct = PXResult<CROpportunityProducts>.Current;
if (opProduct == null)
{
return;
}
var opProductExt = PXCache<CROpportunityProducts>.GetExtension<CROpportunityProductsExt>(opProduct);
var soLineExt = PXCache<SOLine>.GetExtension<SOLineExt>(soLine);
//Copy all extension fields here...
});
});
del(param);
}

Related

How to default the Project Description with the "CustomerID"

I am attempting to insert the Customer ID at the beginning of the Project Description. I believe the two opportunities to do this are 1) upon selection of a customer and 2) upon selection of template if the customer has already been selected. I also believe that I want to extend the PM.ProjectEntry business logic. I am having two issues:
Using the same code from the source code in the extension is giving me an error when I try to select the customer record on the CustomerID field updated event. The error is identified in the code below.
I have an TemplateID field updated event and what I thought was an extension of the DefaultFromTemplateProjectSettings event. No errors but the description field does not get modified.
namespace PX.Objects.PM
{
public class ProjectEntry_Extension : PXGraphExtension<PX.Objects.PM.ProjectEntry>
{
#region Event Handlers
// Attempt to modify the project description line after a customer has been selected
protected virtual void _(Events.FieldUpdated<PMProject, PMProject.customerID> e, PXFieldUpdated baseMethod)
{
baseMethod(e.Cache, e.Args);
if (e.Row != null)
{
// Line 3 of this select statement errors with:
// Argument 1: cannot convert from 'PX.Objects.PM.ProjectEntry_Extension' to 'PX.Data.PXGraph'
Customer customer = new PXSelect<Customer,
Where<Customer.bAccountID,
Equal<Required<Customer.bAccountID>>>>(this).Select(e.Row.CustomerID);
if (customer != null)
{
e.Cache.SetValueExt<PMProject.description>(e.Row, customer.AcctCD + " " + e.Row.Description);
}
}
}
// Two attempts to modify the project descrition after the project template has been selected
// Neither of these two codes error, they just don't update the project description field
// "customer.AcctCD" would be determined similar to the above process that currently errors
protected virtual void DefaultFromTemplateProjectSettings(PMProject prj, PMProject templ)
{
prj.Description = "customer.AcctCD" + ": " + templ.Description;
}
protected virtual void _(Events.FieldUpdated<PMProject, PMProject.templateID> e, PXFieldUpdated baseMethod)
{
baseMethod(e.Cache, e.Args);
if (e.Row != null)
{
e.Cache.SetValueExt<PMProject.description>(e.Row, "customer.AcctCD" + ": " + e.Row.Description);
}
}
#endregion
}
}
In the PM.ProjectEntry business logic, I found the following:
protected virtual void OnDefaultFromTemplateTasksInserted(ProjectEntry target, Dictionary<int, int> taskMap)
{
//this method is used to extend DefaultFromTemplate in Customizations.
}
So, I tried the following in my customization. There are no errors but it doesn't alter the project description when the template is selected.
protected virtual void OnDefaultFromTemplateTasksInserted(PMProject prj, PMProject templ, Dictionary<int, int> taskMap)
{
prj.Description = "CustomerID: " + templ.Description;
}
Without the "CustomerID: ", that is the line of code used in the DefaultFromTemplateProjectSettings object.
Regarding the first error, you simply need to change the line
Customer customer = new PXSelect<Customer,
Where<Customer.bAccountID,
Equal<Required<Customer.bAccountID>>>>(this).Select(e.Row.CustomerID);
to
Customer customer = new PXSelect<Customer,
Where<Customer.bAccountID,
Equal<Required<Customer.bAccountID>>>>(this.Base).Select(e.Row.CustomerID);
You need to pass the PXGraph instance to the PXSelect constructor and you currently are passing this which is PXGraphExtension.
Regarding the second part, I don't think anonymous event handlers have the concept of the BaseMethod, so I would try to remove the second parameter of that function.
With a little help I have a solution.
namespace PX.Objects.PM
{
public class ProjectEntry_Extension : PXGraphExtension<PX.Objects.PM.ProjectEntry>
{
#region Event Handlers
// Attempt to modify the project description line after a customer has been selected
protected virtual void _(Events.FieldUpdated<PMProject, PMProject.customerID> e, PXFieldUpdated baseMethod)
{
baseMethod(e.Cache, e.Args);
if (e.Row != null) // attempted to prevent repatition -- && e.Row.Description.IndexOf(":") > 0
{
Customer customer = new PXSelect<Customer,
Where<Customer.bAccountID,
Equal<Required<Customer.bAccountID>>>>(this.Base).Select(e.Row.CustomerID);
if (customer != null)
{
e.Cache.SetValueExt<PMProject.description>(e.Row, customer.AcctCD + ": " + e.Row.Description);
}
}
}
// Attempt to modify the project description line after a template has been selected
public delegate void DefaultFromTemplateDelegate(PMProject prj, int? templateID, ProjectEntry.DefaultFromTemplateSettings settings);
[PXOverride]
public void DefaultFromTemplate(PMProject prj, int? templateID, ProjectEntry.DefaultFromTemplateSettings settings, DefaultFromTemplateDelegate baseMethod)
{
baseMethod(prj, templateID, settings);
if (Base.Project.Current.CustomerID != null)
{
Customer customer = new PXSelect<Customer,
Where<Customer.bAccountID,
Equal<Required<Customer.bAccountID>>>>(this.Base).Select(prj.CustomerID);
if (customer != null)
{
Base.Project.Current.Description = customer?.AcctCD + ": " + Base.Project.Current.Description;
}
}
}
#endregion
}
}

How to update a custom field values while confirm shipment action is running in Acumatica

we have requirement to duplicate the confirm shipment action button for some business work and also need to update some custom fields on confirm shipment long run operation is completed.
Below is my code but while doing cache update i am getting Error: Collection was modified; enumeration operation may not execute.
Please correct me where i am doing wrong
public PXAction<PX.Objects.SO.SOShipment> ConfirmShipment;
[PXUIField(DisplayName = "Confirm Shipment")]
[PXButton]
protected virtual IEnumerable confirmShipment(PXAdapter adapter)
{
if (ShipFilter.Current != null)
{
var soOrderShip = Base.Document.Current;
if (soOrderShip != null)
{
var graph = PXGraph.CreateInstance<SOShipmentEntry>();
//We are recreating an adapter like the framework would do.
var a = new PXAdapter(graph.Document)
{
Searches = new object[] { soOrderShip.ShipmentNbr }
};
using (PXTransactionScope ts = new PXTransactionScope())
{
//Note: Confirm Shipment is Action 1 :
a.Arguments.Add("actionID", 1);
PXLongOperation.StartOperation(Base, () => { foreach (SOShipment soShipment in graph.action.Press(a)) ; });
//PXLongOperation.WaitCompletion(graph.UID);
PXAutomation.CompleteAction(graph);
PXLongOperation.WaitCompletion(graph.UID);
PXLongOperation.ClearStatus(graph.UID);
graph.Document.Cache.SetValueExt<SOShipmentExt.usrKWMXDCTimeStamp>(soOrderShip, Convert.ToDateTime(Convert.ToDateTime(new PX.Data.PXGraph().Accessinfo.BusinessDate).ToShortDateString() + " " + PX.Common.PXTimeZoneInfo.Now.ToLongTimeString()));
graph.Document.Cache.SetValueExt<SOShipmentExt.usrKWMXPieceCount>(soOrderShip, Convert.ToDecimal(Base.Document.Current.ShipmentQty));
graph.Document.Cache.SetValueExt<SOShipmentExt.usrKWMXEnteredBy>(soOrderShip, this.ShipFilter.Current.EnteredBy);
graph.Document.Update(soOrderShip);
graph.Save.Press();
ts.Complete();
}
}
}
return adapter.Get();
}
Thanks in advance.
You should override the Confirmation routine, execute the Base operation, and then add your code.
Extend existing event

Need to use updated Requested Date on Prepare Invoice process

I have a customization to the Sales Orders screen, where I use a RowPersisting event to update the Requested Date to the current date upon saving. The problem is, if the save is not executed, but the Actions.PrepareInvoice is initiated, the new Requested Date (hopefully set by the RowPersisting event) is not used. I've tried to override the base method as follows (to save the Sales Order record with the new Requested Date before the Prepare Invoice process is run):
public delegate IEnumerable PrepareInvoiceDelegate(PXAdapter adapter);
[PXOverride]
public IEnumerable PrepareInvoice(PXAdapter adapter, PrepareInvoiceDelegate baseMethod)
{
Base.Actions.PressSave();
return baseMethod(adapter);
}
But I receive the following error - "Error: The previous operation has not been complete yet."
How can I ensure that a modified Requested Date is used for the Prepare Invoice process if the Sales Order record is not yet saved?
The PrepareInvoice method in case if the call is not from Processing Page(adapter.MassProcess==false) is calling this.Save.Press() anyway.
Below is the code from PrepareInvoice action. Before creation of the Invoice there is being called this.Save.Press() so any your update will be saved and used for creation of the invoice.
[PXUIField(DisplayName = "Prepare Invoice", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select, Visible = false), PXButton]
public virtual IEnumerable PrepareInvoice(PXAdapter adapter)
{
List<SOOrder> list = adapter.Get<SOOrder>().ToList<SOOrder>();
foreach (SOOrder current in list)
{
if (this.Document.Cache.GetStatus(current) != PXEntryStatus.Inserted)
{
this.Document.Cache.SetStatus(current, PXEntryStatus.Updated);
}
}
if (!adapter.MassProcess)
{
try
{
this.RecalculateAvalaraTaxesSync = true;
this.Save.Press();
}
finally
{
this.RecalculateAvalaraTaxesSync = false;
}
}
PXLongOperation.StartOperation(this, delegate
{
DocumentList<ARInvoice, SOInvoice> documentList = new DocumentList<ARInvoice, SOInvoice>(PXGraph.CreateInstance<SOShipmentEntry>());
SOOrderEntry.InvoiceOrder(adapter.Arguments, list, documentList, adapter.MassProcess);
if (!adapter.MassProcess && documentList.Count > 0)
{
using (new PXTimeStampScope(null))
{
SOInvoiceEntry sOInvoiceEntry = PXGraph.CreateInstance<SOInvoiceEntry>();
sOInvoiceEntry.Document.Current = sOInvoiceEntry.Document.Search<ARInvoice.docType, ARInvoice.refNbr>(documentList[0].DocType, documentList[0].RefNbr, new object[]
{
documentList[0].DocType
});
throw new PXRedirectRequiredException(sOInvoiceEntry, "Invoice");
}
}
});
return list;
}
The solution seems to be to recreate the Action method in a graph extension and add the following code:
if (!adapter.MassProcess)
{
//****Code added to update the Requested Date to today's date...
var soorder = (SOOrder)Base.Caches[typeof(SOOrder)].Current;
soorder.RequestDate = DateTime.Now;
Base.Caches[typeof(SOOrder)].Update(soorder);
//****End of code added...
try
{
Base.RecalculateAvalaraTaxesSync = true;
Base.Save.Press();
}
finally
{
Base.RecalculateAvalaraTaxesSync = false;
}
}

Update Project's attribute value from Different Page

I need help to update the value of Project's attribute from different page.
I have fetched the attribute value in 'Appointments' page using following code.
protected void FSAppointment_RowSelected(PXCache cache, PXRowSelectedEventArgs e, PXRowSelected InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (FSAppointment)e.Row;
AppointmentEntry graph = (AppointmentEntry)cache.Graph;
if (graph.ServiceOrderRelated.Current != null)
{
int? projectID = graph.ServiceOrderRelated.Current.ProjectID;
ProjectEntry projectGraph = PXGraph.CreateInstance<ProjectEntry>();
projectGraph.Project.Current = projectGraph.Project.Search<PMProject.contractID>(projectID);
foreach (CSAnswers att in projectGraph.Answers.Select())
{
if (att.AttributeID == "ESTHOURS")
{
cache.SetValueExt<FieldService.ServiceDispatch.FSAppointmentExt.usrProjectEstimatedRemainingHours>(row, att.Value);
return;
}
}
}
}
And, now I want user to be able to update that particular attribute's value from 'Appointments' page.
For that, I had written following code by overriding the Persist method of 'Appointments' page.
public delegate void PersistDelegate();
[PXOverride]
public void Persist(PersistDelegate baseMethod)
{
if (Base.ServiceOrderRelated.Current != null)
{
using (PXTransactionScope scope = new PXTransactionScope())
{
int? projectID = Base.ServiceOrderRelated.Current.ProjectID;
ProjectEntry projectGraph = PXGraph.CreateInstance<ProjectEntry>();
var project = projectGraph.Project.Search<PMProject.contractID>(projectID);
var answers = projectGraph.Answers.Select();
foreach (CSAnswers att in answers)
{
if (att.AttributeID == "ESTHOURS")
{
att.Value = "20";
}
}
projectGraph.Actions.PressSave();
}
}
baseMethod();
}
But still it is not updating the value.
The thing to realize is that the attribute system is different that the usual DAC->SQL system. CSAnswers is a unnormalized table of values for all the attributes. They are linked to a DAC document by RefNoteID. See select * from CSAnswers where AttributeID = 'ESTHOURS' In the code above you are altering every project's 'esthours'. You're also missing a statement where you tell the graph to update the cache with your altered object. Something like projectGraph.Answers.Update(att);

Make Salesperson ID a Required field on SOLine

I need to make Salesperson ID on SOLine as a required field. But as Transfer orders do not have Salesperson, hence it should only validate when I create orders other than Transfer orders.
I tried with below code but it seems it is not working. Might be it is overrided with some existing code. Let me know if anyone has any suggestions.
public PXSetup<SOOrderTypeOperation,
Where<SOOrderTypeOperation.orderType, Equal<Optional<SOOrderType.orderType>>,
And<SOOrderTypeOperation.operation, Equal<Optional<SOOrderType.defaultOperation>>>>> sooperation;
protected bool IsTransferOrder
{
get
{
return (sooperation.Current.INDocType == INTranType.Transfer);
}
}
protected virtual void SOLine_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
var row = (SOLine)e.Row;
if (row == null) return;
PXDefaultAttribute.SetPersistingCheck<SOLine.salesPersonID>(sender, row, IsTransferOrder ? PXPersistingCheck.Nothing : PXPersistingCheck.Null);
}
I usually thrown an appropriate exception in Row Persisting when the condition exists.
Here is an example from SOShipmentEntry checking for transfer and checking the null value of a field:
protected virtual void SOShipment_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
SOShipment doc = (SOShipment)e.Row;
if (doc.ShipmentType == SOShipmentType.Transfer && doc.DestinationSiteID == null)
{
throw new PXRowPersistingException(typeof(SOOrder.destinationSiteID).Name, null, ErrorMessages.FieldIsEmpty, typeof(SOOrder.destinationSiteID).Name);
}
}
I have also called RaiseExceptionHandling similar to this example within RowPersisting
// sender = PXCache
if (row.OrderQty == Decimal.Zero)
sender.RaiseExceptionHandling<POLine.orderQty>(row, row.OrderQty, new PXSetPropertyException(Messages.POLineQuantityMustBeGreaterThanZero, PXErrorLevel.Error));
Both examples should stop the page from the save. calling the Raise Exception handling should point out the field with the Red X which is the better approach and easier for the user to find the field in question.
For your example:
protected virtual void SOLine_RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
SOLine row = (SOLine)e.Row;
if (row == null)
{
return;
}
if (!IsTransferOrder && row.SalesPersonID == null)
{
sender.RaiseExceptionHandling<SOLine.salesPersonID>(row, row.SalesPersonID, new PXSetPropertyException(ErrorMessages.FieldIsEmpty, PXErrorLevel.Error));
}
}

Resources