A couple of revisions ago, Acumatica Deprecated and removed the FSxARTran projection.
I thought I had worked around all of the issues this created, but one came back to bite me recently.
The use case is that we have a custom field called UsrASGPrintCheck which is a field we added to Inventory Items, Service Order line items, Appointment Line items, and ARTran Line Items.
The idea is that the customer can specify at the inventory level if it should default to being printed on an invoice. It can be overridden at the Service Order level, and again at the appointment level.
I have that working part fine.
The issue is when thy go to print the invoice.
In the Appointment, when the “RUN BILLING” button is hit, it runs a method called InvoiceAppointment() which transfers the appointment detail to the ARTran database, and then runs the ARInvoiceEntry graph.
But it is creating the records there with the default value from inventory and not the FSSOdet of the appointment (naturally since that is the default behavior...)
The issue is that when I try to intercept the event in the ARInvoiceEntry to fix it, there is no refNbr to access. It is simply <NEW> until the record gets saved, but that is after it exits the ARInvoiceEntry graph (apparently).
Without a refNbr, I cannot get the appointment’s FSSODET line. I need to look up the associated appointment, but I don’t see that anywhere in the ARInvoiceEntry graph’s cache.
We used to use FSxARTran here as a cross reference, which would allow us to get the FSService Order Information that linked to the FSARtran, but that is gone now. (I also think they changed the order in which RefNbr gets assigned. It seems that FSxARTran was created sooner… but that is a guess)
Anyway… Does anyone have any idea how I can override the InvoiceAppointment() method, grabbing the appointment detail, and setting my flag before it brings the ARInvoiceEntry graph up to the user?
If it helps this is the InvoiceAppointment() method in the source code that gets called when the button is clicked.
public PXAction<FSAppointment> invoiceAppointment;
[PXButton]
[PXUIField(DisplayName = "Run Billing", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
public IEnumerable InvoiceAppointment(PXAdapter adapter)
{
List<FSAppointment> list = adapter.Get<FSAppointment>().ToList();
List<AppointmentToPost> rows = new List<AppointmentToPost>();
if (!adapter.MassProcess)
{
SaveWithRecalculateExternalTaxesSync();
}
if (ServiceOrderTypeSelected.Current != null && ServiceOrderRelated.Current != null
&& ServiceOrderTypeSelected.Current.PostTo == ID.Batch_PostTo.SO)
{
ValidateContact(ServiceOrderRelated.Current);
}
foreach (FSAppointment fsAppointmentRow in list)
{
// Acuminator disable once PX1008 LongOperationDelegateSynchronousExecution [compatibility with legacy code]
PXLongOperation.StartOperation(
this,
delegate ()
{
SetServiceOrderStatusFromAppointment(ServiceOrderRelated.Current, fsAppointmentRow, ActionButton.InvoiceAppointment);
CreateInvoiceByAppointmentPost graphCreateInvoiceByAppointmentPost = PXGraph.CreateInstance<CreateInvoiceByAppointmentPost>();
graphCreateInvoiceByAppointmentPost.Filter.Current.PostTo = ServiceOrderTypeSelected.Current.PostTo == ID.SrvOrdType_PostTo.ACCOUNTS_RECEIVABLE_MODULE ? ID.Batch_PostTo.AR_AP : ServiceOrderTypeSelected.Current.PostTo;
graphCreateInvoiceByAppointmentPost.Filter.Current.IgnoreBillingCycles = true;
graphCreateInvoiceByAppointmentPost.Filter.Current.BranchID = fsAppointmentRow.BranchID;
graphCreateInvoiceByAppointmentPost.Filter.Current.LoadData = true;
if (fsAppointmentRow.ActualDateTimeEnd > Accessinfo.BusinessDate)
{
graphCreateInvoiceByAppointmentPost.Filter.Current.UpToDate = fsAppointmentRow.ActualDateTimeEnd;
graphCreateInvoiceByAppointmentPost.Filter.Current.InvoiceDate = fsAppointmentRow.ActualDateTimeEnd;
}
graphCreateInvoiceByAppointmentPost.Filter.Insert(graphCreateInvoiceByAppointmentPost.Filter.Current);
AppointmentToPost appointmentToPostRow = graphCreateInvoiceByAppointmentPost.PostLines.Current =
graphCreateInvoiceByAppointmentPost.PostLines.Search<AppointmentToPost.refNbr>(fsAppointmentRow.RefNbr, fsAppointmentRow.SrvOrdType);
if (appointmentToPostRow == null)
{
throw new PXSetPropertyException(TX.Error.DocumentCannotBeInvoiced, fsAppointmentRow.SrvOrdType, fsAppointmentRow.RefNbr);
}
rows = new List<AppointmentToPost>
{
appointmentToPostRow
};
Guid currentProcessID = graphCreateInvoiceByAppointmentPost.CreateInvoices(graphCreateInvoiceByAppointmentPost, rows, graphCreateInvoiceByAppointmentPost.Filter.Current, adapter.QuickProcessFlow, false);
if (graphCreateInvoiceByAppointmentPost.Filter.Current.PostTo == ID.SrvOrdType_PostTo.SALES_ORDER_MODULE
|| graphCreateInvoiceByAppointmentPost.Filter.Current.PostTo == ID.SrvOrdType_PostTo.SALES_ORDER_INVOICE)
{
foreach (PXResult<FSPostBatch> result in SharedFunctions.GetPostBachByProcessID(this, currentProcessID))
{
FSPostBatch fSPostBatchRow = (FSPostBatch)result;
graphCreateInvoiceByAppointmentPost.ApplyPrepayments(fSPostBatchRow);
}
}
AppointmentEntry apptGraph = PXGraph.CreateInstance<AppointmentEntry>();
apptGraph.AppointmentRecords.Current =
apptGraph.AppointmentRecords.Search<FSAppointment.refNbr>
(fsAppointmentRow.RefNbr, fsAppointmentRow.SrvOrdType);
if (!adapter.MassProcess || this.IsMobile == true)
{
using (new PXTimeStampScope(null))
{
apptGraph.AppointmentPostedIn.Current = apptGraph.AppointmentPostedIn.SelectWindowed(0, 1);
apptGraph.openPostingDocument();
}
}
});
}
return list;
}I came up with something like this in the AppointmentEntry Graph, but Acuminator was complaining about my method signature, and I wasn’t sure what to change it to. I could disable the error check, but didn’t know if that would cause other problems:
// Override Invoice Method so we can set the appropriate values for our PrintFlag
// Override the InvoiceAppointment action
// Acuminator disable once PX1096 PXOverrideSignatureMismatch [Doesn't like method]
[PXOverride]
public IEnumerable InvoiceAppointment(PXAdapter adapter, Func<PXAdapter, IEnumerable> baseMethod)
{
// Access the selected appointments
List<FSAppointment> list = adapter.Get<FSAppointment>().ToList();
foreach (FSAppointment appointment in list)
{
// Add custom logic here if needed
}
// Call the base method to retain existing functionality
IEnumerable result = baseMethod(adapter);
// Your custom logic AFTER the base method runs
foreach (FSAppointment appointment in list)
{
// Modify ARTran records or any post-processing logic here
UpdateARTranPrintCheck(appointment);
}
return result;
}
