How to assign Invoice RefNbr before saving the invoice - acumatica

I have set my invoice to Manual Numbering
And I want to assign the invoice number(RefNbr) - if blank, before saving the invoice (AR301000). I've overridden the RowPersisting event as follow:
public class ARInvoiceEntry_Extension:PXGraphExtension<ARInvoiceEntry>
{
protected void ARInvoice_RowPersisting(PXCache cache, PXRowPersistingEventArgs e, PXRowPersisting InvokeBaseHandler)
{
var row = (ARInvoice)e.Row;
if (row != null)
{
//BB-<timestamp> as inv# for testing only
if (string.IsNullOrEmpty(row.RefNbr))
row.RefNbr = "BB-" + DateTime.Now.ToString("hhmmsstt");
}
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
}
}
If I raised a new invoice w/out any lines and saved it. It correctly assigned the Reference Nbr as expected.
Problem is if I raised a new invoice WITH lines. Its complaining that the RefNbr is blank.
What am I missing ? How do I fix this ? Thanks.

Acumatica's PXDBDefaultAttribute only updates field of the dependent records when both the source field of the parent record and the foreign key field of the dependent record are not empty.
When a dependent record is inserted into the cache, PXDBDefaultAttribute will copy the source field value from the parent record to the field it decorates from the dependent record. While the framework is saving new parent record into the database, it first raises RowPersisting event for the parent record. PXDBDefaultAttribute subscribes to the RowPersisting event of the parent record type to save the original source field value:
to locate the parent record after it was saved to the database
or to use it as a restore point for the dependent field in case of an aborted transaction
Also, PXDBDefaultAttribute subscribes to the RowPersisting event of the dependent record type to update the foreign key field with the actual source field value, that has just been recorded in the database. To update the foreign key field PXDBDefaultAttribute must first locate the parent record using the original source field value obtained earlier in the RowPersisting event handler of the parent record type. In case the foreign key field value appears to be empty, there is no chance for PXDBDefaultAttribute to locate the parent record for it and it simply leaves the dependent field empty. This is what eventually is causing the error "RefNbr cannot be empty".
With all that being said, I believe it won't be possible to achieve the desired results if leaving AR Invoice Reference Number empty until it gets saved to the database. As an alternative, let me suggest to default AR Invoice Reference Number to some constant, like < ENTER >, and at the same time replace it with the actual number within the ARInvoice_RowPersisting handler:
using PX.Data;
using System;
namespace PX.Objects.AR
{
public class ARInvoiceNumberingCstAttribute : ARInvoiceType.NumberingAttribute
{
public const string EnterRefNbr = "<ENTER>";
protected override string GetNewNumber()
{
string newNumber = base.GetNewNumber();
if (string.IsNullOrEmpty(newNumber))
{
newNumber = EnterRefNbr;
}
return newNumber;
}
public override void RowPersisting(PXCache sender, PXRowPersistingEventArgs e)
{
if (GetNewNumber() == EnterRefNbr) return;
base.RowPersisting(sender, e);
}
}
public class ARInvoiceEntry_Extension : PXGraphExtension<ARInvoiceEntry>
{
[PXRemoveBaseAttribute(typeof(ARInvoiceType.NumberingAttribute))]
[PXMergeAttributes(Method = MergeMethod.Append)]
[ARInvoiceNumberingCst]
protected void ARInvoice_RefNbr_CacheAttached(PXCache sender)
{ }
protected void ARInvoice_RowPersisting(PXCache cache, PXRowPersistingEventArgs e,
PXRowPersisting InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (ARInvoice)e.Row;
if (row != null)
{
//BB-<timestamp> as inv# for testing only
if (row.RefNbr == ARInvoiceNumberingCstAttribute.EnterRefNbr)
row.RefNbr = "BB-" + DateTime.Now.ToString("hhmmsstt");
}
}
}
}

Related

BQL expression in FieldDefaulting for Current User

I'm trying to set the default value of a custom field to the defContactID of the currently logged in user. I cannot get the BQL correct, any advise?
namespace PX.Objects.PJ.DailyFieldReports.PJ.Graphs
{
public class DailyFieldReportEntry_Extension : PXGraphExtension<DailyFieldReportEntry>
{
protected void DailyFieldReport_UsrOwner_FieldDefaulting(PXCache cache, PXFieldDefaultingEventArgs e, PXFieldDefaulting InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = e.Row as DailyFieldReport;
//BQL Statement to pull the EPEmployee record for the currently logged in employee
e.NewValue = EPEmployee.defContactID;
}
}
}
To get the logged in user details you can use the AccessInfo object which is in every graph. Accessinfo.UserContactID will retrieve the logged in user contact ID.
you can also use this by specifying it in the PXDefault attribute, without having the need to use the field event. In this case:
[PXDefault(typeof(AccessInfo.userContactID))

Copying the value from a custom field to TaxRegistrationID field in RowPersisting event

I'm trying to copy the value from a custom field called "customerExt.UsrRfc" to TaxRegistrationID field but it doesn't work in Customers screen, I'm using Customer_RowPersisting event handler.
This is the customerExt.UsrRfc field:
This is the TaxRegistrationID field:
This is the RowPersisting event:
protected void Customer_RowPersisting(PXCache cache, PXRowPersistingEventArgs e)
{
Customer row = (Customer)e.Row;
if (row == null)
{
return;
}
var customerExt = row.GetExtension<BAccountExt>();
row.TaxRegistrationID = customerExt.UsrRfc;
}
I tried to copy the value to another field like "Account Ref #" and it works fine.
Can you help me with this?
The TaxRegistrationID field in the screenshot is from Location DAC instead of Customer:
You need to change the solution to update the field in the correct view.

Global Generic Field Update Event

I have 50+ custom paired fields "Inches" and "Centimeters", each enabled and editable. I need to update "Inches" if the user changed the value of "Centimeters" and visa verse. I was able to do this using SetValuePending on one of the paired fields and SetValueExt on the other during the Field Updated Event. My question, is there a way to do this on a higher level without having to do a Field_Updated event for all the 100+ fields. I know that Formulas would create a circular reference so cannot be used. Thanks
Well, you can use one method to handle FieldUpdated events for all fields you need using graph FieldUpdated.AddHandler method in constructor. To get a field name just extend a standard Acumatica FieldUpdated delegate with one additional parameter (name for example) and put it during the FieldUpdated.AddHandler call.
Here is an example with "Invoices and Memos" screen and ARInvoiceEntry graph.
public ARInvoiceEntry()
{
FieldUpdated.AddHandler(typeof(ARTran), typeof(ARTran.inches).Name, (sender, e) => CommonFieldUpdated(sender, e, typeof(ARTran.inches).Name));
FieldUpdated.AddHandler(typeof(ARTran), typeof(ARTran.centimeters).Name, (sender, e) => CommonFieldUpdated(sender, e, typeof(ARTran.centimeters).Name));
...
}
protected virtual void CommonFieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e, string name)
{
// do something
}
Moreover, you can add handlers dynamically using fields collection for example
foreach(var field in Caches[typeof(ARTran)].Fields)
{
// add handler depends on field name
}
You can do it by PXFormula + ExternalValue BQL expression (the same as PXRowUpdatedEventArgs.ExternalCall for example), which will prevent circular reference between pair fields. The idea is to calculate field only when a related field has been changed by the user from UI (ExternalCall = true) and skip calculation when related field updated by the formula (ExternalCall = false).
public class centimetersInInches : PX.Data.BQL.BqlDecimal.Constant<centimetersInInches>
{
public centimetersInInches() : base(2.54m) { }
}
[PXDecimal]
[PXUIField(DisplayName = "Inches")]
[PXUnboundDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(ExternalValue<Div<centimeters, centimetersInInches>>))]
public virtual decimal? Inches { get; set; }
public abstract class inches : PX.Data.BQL.BqlDecimal.Field<inches> { }
[PXDecimal]
[PXUIField(DisplayName = "Centimeters")]
[PXUnboundDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(ExternalValue<Mult<inches, centimetersInInches>>))]
public virtual decimal? Centimeters { get; set; }
public abstract class centimeters : PX.Data.BQL.BqlDecimal.Field<centimeters> { }
And aspx
<px:PXGridColumn DataField="Inches" CommitChanges="True" />
<px:PXGridColumn DataField="Centimeters" CommitChanges="True" />
You can use the RowUpdated event and compare the old row with the new row to detect which field changed. I agree that keeping all logic in a single method is preferable.
public virtual void DAC_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
DAC row = e.Row as DAC;
DAC oldRow = e.OldRow as DAC;
if (row == null || oldRow == null) return;
// Compare old row with new row to determine which field changed
if (row.Inches != oldRow.Inches)
{
// Inches field changed, update CM value
row.CM = row.Inches * INCHES_TO_CM_CONSTANT;
}
// Add more conditions for the other fields
[..]
}
I know that Formulas would create a circular reference so cannot be
used.
Yes I wouldn't recommend it either, you could use the DAC property Setter though.
public string _Inches
public virtual string Inches
{
get
{
return this._Inches;
}
set
{
this._Inches = value;
this.CM = value * INCHES_TO_CM_CONSTANT;
}
}
For all solution (except Formula/DAC attributes) I think a condition to stop recursion should be possible if it's absolutely necessary:
if (this.CM != value * INCHES_TO_CM_CONSTANT)
this.CM = value * INCHES_TO_CM_CONSTANT;
Ideally proper use/avoidance of SetValueExt to control when events are raised (Ext method raises events) would be enough to stop infinite loops.

How to Count records in a related table and allow user to override

I have a custom field on the CRActivity table in which I need to store the number of records in a related table. I am trying to set the field to the value when screen CR306030 opens. The user needs to be able to override the calculated number so, I'm thinking that I need logic on the calculation to check if the custom field is > 0, in which case, don't populate the custom field and assume it's already been set.
Previously, I've tried to do this in the Field_Selecting events but, this is not working. I'm thinking I might be able to use a PXFormula attribute. Any suggestions?
I tried making a custom attribute which is close but, it won't save the values to the db. The save button enables, I can click it and it looks like it saves but, no dice. Some mundane detail, I'm sure.....
Here's my custom attribute:
public class CFCountIfZeroAttribute : PXIntAttribute
{
public override void FieldSelecting(PXCache cache, PXFieldSelectingEventArgs e)
{
if (e.Row == null)
return;
CRActivity activity = (CRActivity)e.Row;
CRActivityExt activityExt = activity.GetExtension<CRActivityExt>();
if (activityExt.usrCustomField <= 0)
{
int aggregateValue = BQLToFind();
e.ReturnValue = aggregateValue;
cache.SetValue<CRActivityExt.usrCustomField>(e.Row, aggregateValue);
cache.IsDirty = true;
}
}
}
Thanks!
I don't think I've ever done a count within a PXFormula, but what if you created a custom attribute?
[DBUIInt()]
[EditableCount()]
public virtual int? CountField
public class EditableCountAttribute
{
public override void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (e.Row != null)
{
//if CountField is 0, lookup the count from another table
}
}
}
This is just off the top of my head. You could pass the field you're counting into the attribute if you wanted to do this elsewhere.

Setting the default the value from one grid to another grid in same screen

I tried but the value is getting updated when I selected any filed in the grid then say save.
protected void TSFormulaByProds_DefaultSiteID_FieldSelecting(PXCache cache, PXFieldSelectingEventArgs e)
{
var row = (TSFormulaByProds)e.Row;
TSFormula tSFormula = PXSelect<TSFormula>.Select(this);
if (tSFormula.DefaultSiteID != null)
{
e.ReturnValue = tSFormula.DefaultSiteID;
}
}
I want to update another grid value before save and after selecting the first field from the grid
If you need to initialize a field FieldDefaulting is the appropriate event.
protected void TSFormulaByProds_DefaultSiteID_FieldDefaulting(PXCache cache, PXFieldDefaultingEventArgs e)
{
e.NewValue = yourValueHere;
}
It will be executed when a new DAC row is inserted.
If you need to re-trigger the Defaulting logic from another event you can do:
object newValue;
Cache[typeof(TSFormulaByProds)].RaiseFieldDefaulting<TSFormulaByProds.DefaultSiteID>(yourTSFormulaByProdsDACRow, out newValue);

Resources