Can I change the business date through BLC code? I know it's in the AccessInfo DAC (AccessInfo.businessDate).
Thanks...
The static PXContext has a public static method called SetBusinessDate, You can use that method to change the Business Date.
public static void SetBusinessDate(DateTime? date)
{
PXContext.PXIdentity.BusinessDate = date;
}
You can simply call PXContext.SetBusinessDate method and force a page refresh after that, otherwise the UI will still show the old business date.
var YOUR_NEW_BUSINESS_DATE = new DateTime(2022,01,01);
PXContext.SetBusinessDate(YOUR_NEW_BUSINESS_DATE);
throw new PXRefreshException();
Related
I am trying to add another address to a Branch in 21.203, similar to the how the DefAddress works. I have extended the Branch DAC and added the field to the extension. I also tried extending the BAccount DAC and table to get this to work but, nothing is working so far. The Address record is being created in the Address table but, the PXDBChildIdentity attribute doesn't seem to be sending back the ID to the Address record. On the screen, when I enter the values for the address, and click Save, all of the values are wiped out and the custom field (UsrCustomAddress) is never populated.
#region UsrCustomAddress
[PXDBInt()]
[PXUIField(DisplayName="Custom Address")]
[PXDBChildIdentity(typeof(Address.addressID))]
public virtual Int32? UsrCustomAddress { get; set; }
public abstract class usrCustomAddress : PX.Data.BQL.BqlInt.Field<usrCustomAddress> { }
#endregion
And, then, I added the View to the BranchMaint extension.
<code>
namespace PX.Objects.CS
{
public class BranchMaint_Extension : PXGraphExtension<BranchMaint>
{
public PXSelect<Address, Where<Address.bAccountID, Equal<Current<BAccount.bAccountID>>,
And<Address.addressID, Equal<Current<BAccountExt.usrCustomAddress>>>>> CustomAddress;
}
}
</code>
Not sure what I'm missing?
TIA!
I handled this by manually calling .Insert() on the specific Data View in the row inserting event and writing it to the field. PXDBChildIdentity then can update the field with the real identity value (on row persist) after the link is established.
protected void _(Events.RowInserting<Branch> e)
{
if (e.Row is null) return;
Address newAddress = CustomAddress.Insert();
BranchExt ext = e.Cache.GetExtension<BranchExt >(e.Row);
ext.UsrCustomAddress = newAddress.AddressID;
}
If anyone knows how to do this in an attribute id love to know. Note, this will only work for newly created records. Any existing records would not have a row inserting event fire and fill your custom field. You need to backfill the data with SQL or just a temporary custom action that you can invoke this code manually.
I need to add a filter to the Process Orders screen grid, so that only orders that have an unpaid balance = 0 will show, based on an additional checkbox to the filter area called 'Must Have Payment' being checked. I thought I had it by adding a where clause to the Orders view, but that didn't work.
[PXFilterable]
public PXFilteredProcessing<SOOrder, SOOrderFilter,
Where<SOOrder.unpaidBalance, NotEqual<Zero>,
Or<Current<SOOrderFilterExt.mustHavePayment>, Equal<False>>>> Orders;
I'm sure I'm doing this incorrectly, as all orders are showing and not just the 'Open' orders as it was before I added this change. I'd like to override the view delegate and modify that to add my filter / condition to the returned rows, but I can't override this method - at least that I can tell.
What's the best way to get this custom filter restriction into the select for that grid?
Thanks much...
Overriding the dataview delegate may not be the best idea on this specific case.
I noticed that AddCommonFilters() method is not private (used in the dataview delegate), so I think you could try to override this method instead, call Base method and then inject your custom code to include your query filter into the main query used on the for each.
Maybe this is something you can use to implement your filtering, see snippet below:
public class SOCreateShipment_Extension : PXGraphExtension<SOCreateShipment>
{
#region Event Handlers
public delegate void AddCommonFiltersDelegate(SOOrderFilter filter,
PXSelectBase<SOOrder> cmd);
[PXOverride]
public void AddCommonFilters(SOOrderFilter filter, PXSelectBase<SOOrder> cmd, AddCommonFiltersDelegate baseMethod)
{
baseMethod(filter,cmd);
//Add your custom code here
if (Yourcondition)
{
cmd.WhereAnd<Where<YOURFILTERINGCondition>>>>();
}
}
........................
Please also review the AlterFilters() method if needed.
We're using the Run Project Billing screen to create records in AR / Invoice and Memo.
In the Invoice & Memo screen, we need the process to populate the header Customer Ord. number, along with a user field that has been added to the grid section on the 'Document Details' tab. At the moment, the process is not doing this.
I'd like to intercept the processing action on the screen using a technique I'm familiar with, namely using an 'AddHandler':
[PXOverride]
protected virtual IEnumerable Items (PXAdapter adapter)
{
PXGraph.InstanceCreated.AddHandler<BillingProcess>((graph) =>
{
graph.RowInserting.AddHandler<BillingProcess.ProjectsList>((sender, e) =>
{
//Custom logic goes here
});
});
return Base.action.Press(adapter);
}
I see no Base.Actions that remotely resembles 'Bill' or 'Bill All'.
This is obviously not exactly the code I need, but I would think this is the general place to start.
After reviewing the source business logic, I don't see any 'Bill' or 'Bill All' Actions - or any 'Actions' at all (baffling). I see an IEnumerable method called 'items', so that's what I started with above.
Is this the correct way to go about this?
Update: 2/14/2017
Using the answer provided re: the overridden method InsertTransaction(...) I've tried to set our ARTran user field (which is required) using the following logic:
PMProject pmproj = PXSelect<PMProject, Where<PMProject.contractID, Equal<Required<PMProject.contractID>>>>.Select(Base, tran.ProjectID);
if (pmproj == null) return;
PMProjectExt pmprojext = PXCache<PMProject>.GetExtension<PMProjectExt>(pmproj);
if (pmprojext == null) return;
ARTranExt tranext = PXCache<ARTran>.GetExtension<ARTranExt>(tran);
if (tranext == null) return;
tranext.UsrContractID = pmprojext.UsrContractID;
Even though this sets the user field to the correct value, it still gives me an error that the required field is empty when the process finishes. My limited knowledge prevents me from understanding why.
On the Run Project Billing screen, captions of Process and Process All buttons were changed to Bill and Bill All respectively in BLC constructor.
Process delegate is set for Items data view within the BillingFilter_RowSelected handler:
public class BillingProcess : PXGraph<BillingProcess>
{
...
public BillingProcess()
{
Items.SetProcessCaption(PM.Messages.ProcBill);
Items.SetProcessAllCaption(PM.Messages.ProcBillAll);
}
...
protected virtual void BillingFilter_RowSelected(PXCache cache, PXRowSelectedEventArgs e)
{
BillingFilter filter = Filter.Current;
Items.SetProcessDelegate<PMBillEngine>(
delegate (PMBillEngine engine, ProjectsList item)
{
if (!engine.Bill(item.ProjectID, filter.InvoiceDate, filter.InvFinPeriodID))
{
throw new PXSetPropertyException(Warnings.NothingToBill, PXErrorLevel.RowWarning);
}
});
}
...
}
As code snippet above confirms, all records in the AR Invoice and Memos screen are created by instance of the PMBillEngine class. Below is code snippet showing how to override InsertNewInvoiceDocument and InsertTransaction methods within the PMBillEngine BLC extension:
public class PMBillEngineExt : PXGraphExtension<PMBillEngine>
{
public delegate ARInvoice InsertNewInvoiceDocumentDel(string finPeriod, string docType, Customer customer,
PMProject project, DateTime billingDate, string docDesc);
[PXOverride]
public ARInvoice InsertNewInvoiceDocument(string finPeriod, string docType, Customer customer, PMProject project,
DateTime billingDate, string docDesc, InsertNewInvoiceDocumentDel del)
{
var result = del(finPeriod, docType, customer, project, billingDate, docDesc);
// custom logic goes here
return result;
}
[PXOverride]
public void InsertTransaction(ARTran tran, string subCD, string note, Guid[] files)
{
// the system will automatically invoke base method prior to the customized one
// custom logic goes here
}
}
Run Project Billing process invokes InsertNewInvoiceDocument method to create new record on the AR Invoice and Memos screen and InsertTransaction method to add new invoice transaction.
One important thing to mention: overridden InsertNewInvoiceDocument and InsertTransaction methods will be invoked when a user launches Run Project Billing operation either from the processing Run Project Billing screen or from the data entry Projects screen.
For more information on how to override virtual BLC methods, see Help -> Customization -> Customizing Business Logic -> Graph -> To Override a Virtual Method available in every Acumatica ERP 6.1 website
I'm confused with how to synchronise data to the query database.
Let's say I have an aggregate: CreditAccount and some commands may produce CreditAccountBalanceChangedEvent:
public class CreditAccount extends AbstractAnnotatedAggregateRoot<Long> {
#AggregateIdentifier
private Long id;
private int balance;
private DateRange effectiveDateRange;
#CommandHandler
public CreditAccount(CreateCreditAccountCommand command) {
apply(new CreditAccountCreatedEvent(command.getAccountId(),
command.getEffectiveDateRange()));
apply(new CreditAccountBalanceChangedEvent(command.getAccountId(),
command.getAmount()));
}
#EventHandler
private void on(CreditAccountCreatedEvent event) {
this.id = event.getAccountId();
this.effectiveDateRange = event.getEffectiveDateRange();
}
#EventHandler
private void on(CreditAccountBalanceChangedEvent event) {
//notice this line, some domain logic here
this.balance = add(this.balance, event.getAmount());
}
private int add(int current, int amount) {
return current + amount;
}
}
public class CreditAccountBalanceChangedEvent {
private final long accountId;
private final int amount;
//omitted constructors and getters
}
And everything works fine on the command handler side. And I set off to the query side but I find I'm writing some duplicate domain logic here:
#Transactional
#Slf4j
public class CreditAccountEventHandler {
private CreditAccountReadModelStore creditAccountReadModelStore;
#EventHandler
public void handle(CreditAccountCreatedEvent event) {
log.info("Received " + event);
creditAccountReadModelStore.store(accountDevriveFrom(event));
}
#EventHandler
public void handle(CreditAccountBalanceChangedEvent event) {
log.info("Received " + event);
final CreditAccountReadModel account = creditAccountReadModelStore
.findBy(event.getAccountId());
//notice this line, some domain logic here
account.setBalance(account.getBalance() + event.getAmount());
creditAccountReadModelStore.store(account);
}
//omitted setters and private methods
}
As you may notice, I wrote balance calculation code on both command and query side. My question is that is this inevitable in some situations or I write domain logic in wrong place?
As my study so far, events represent something have occured, so no business logic in them, they're just data holder(but reveal users's intent). So should I add a 'balance' field to CreditAccountBalanceChangedEvent and move balance calculation code to command handler method?
public class CreditAccount extends AbstractAnnotatedAggregateRoot<Long> {
//omitted fields
#CommandHandler
public CreditAccount(CreateCreditAccountCommand command) {
apply(new CreditAccountCreatedEvent(command.getAccountId(),
command.getEffectiveDateRange()));
apply(new CreditAccountBalanceChangedEvent(command.getAccountId(),
command.getAmount(), add(this.balance, command.getAmount())));
}
#EventHandler
private void on(CreditAccountBalanceChangedEvent event) {
//notice this line, some domain logic here
//event.getAmount() is no use here, just for auditing?
this.balance = event.getBalance();
}
}
In this case, I can remove balance calculation on the query side by using event.getBalance().
Sorry for a screen full question, any idea is appreciate.
I see two options.
One is for the command to contain the change in balance, the command handler to calculate the new balance, and the event to contain the new balance. If nothing is recalculated in the event handler, it ensures that if the business rules change in the future, they do not affect your object's history when when it is reconstituted from the events.
An alternative would be to place the business rules in a separate class that is called from both the command handler and the event handler to avoid duplication, and then to version those business rules -- via subclassing for example. So you could have an abstract class called CalculateBalanceRule with a subclass of CalculateBalanceRuleVersion1 that is initially referenced by both. If the rule changes, you create CalculateBalanceRuleVersion2, change your command handler to reference it, but keep the reference to Version1 in your event handler, so that it will always replay the rules it did originally.
The second approach is definitely more maintenance, but can answer HOW something change, not simply WHAT changed, if that's something that's important to your business.
Edit: A third option is for the event to only contain the new balance like in the first option, but to version the events. So you have BalanceChangedEvent, BalanceChangedEvent_v2, and so on. This is the direction I could take, as I don't really care to keep a history of how things changed, but I do need to account for the possibility that the events themselves might take on additional members or rename its members. Logic is then needed to determine which event version to use to reconstitute the object at each step.
I have written a workflow for that autofills a column in a sharepoint list. When I add a new entry nothing happens. However, if I go to another list and then return to the list with the workflow I can see that it has worked. I have set up the workflow to start when a new item is created, is there some other setting in the workflow to make it update instantly?
No there is no. You should use SPItemEventReceiver instead and use ItemUpdating method. This is what works instantly and the workflow requires some time to start and it runs asynchronously. Furthermore workflows are heavy and you should avoid using them for very simple actions.
The sample code for an event receiver is
public class MyEventReceiver :SPItemEventReceiver
{
public override void ItemUpdating(SPItemEventProperties properties)
{
UpdateField(properties);
}
public override void ItemAdding(SPItemEventProperties properties)
{
UpdateField(properties);
}
private void UpdateField(SPItemEventProperties properties)
{
EventFiringEnabled = false;
var item = properties.ListItem;
// do calculation here
item.SystemUpdate(false); // this update that is most suitable for automatic updates
EventFiringEnabled = true;
}
}
And then add this event receiver to a list. I hope this link helps
You can also try to add a Calculated column to your list if you cannot use Visual Studio. This way the hardest point is to define a formula that will takes data from other columns of the list item and performs a calculation. link1 and link2 can give you more information on how to write a formula in the calculated column in SharePoint with no code.