How can I set the LocationID on the 'Create Shipment' process from the Sales Orders screen? - acumatica

I have a process that creates records in the Sales Orders screen's Details grid based on two Header user fields: SiteID (Warehouse) and LocationID.
When the 'Create Shipment' process is initiated, the shipment is created which contains the SiteID in the Sales Orders grid - but since there is no locationID in the grid, this 'Create Shipment' process uses some default(?) LocationID, where I'd like to use the Header User field's LocationID.
My question is, how would I intercept this process to set the LocationID to something other than what it's defaulting to?
Thanks...
Update:
Using the virtual method:
SetShipmentFieldsFromOrder(SOOrder order, SOShipment shipment, Nullable<Int32> siteID, Nullable<DateTime> shipDate, String operation, SOOrderTypeOperation orderOperation, Boolean newlyCreated, SetShipmentFieldsFromOrderDelegate baseMethod)
I don't see any way to set the grid value for LocationID (i.e., there is no SOShipLine record to set a value in the virtual method. How would I do this?

There's a virtual method on the SOShipmentEntry graph called SetShipmentFieldsFromOrder, you can override that to update the CustomerLocationID as needed. The create shipment action calls SOShipmentEntry.CreateShipment which inserts the shipment and then calls the SetShipmentFieldsFromOrder method.
The system should be pulling the SOShipment.CustomerLocationID from the SOOrder.CustomerLocationID field by default though.

I believe the question is about defaulting of warehouse locations into the shipment lines and allocations rather than customer locations
Currently, shipment line selects location(s) by the following way
Originally (SelectLocationStatus), it selects location based on their pick priority (smaller value means higher priority)
After this method, the ResortStockForShipmentByDefaultItemLocation is executed. This method puts the default issue location for the item-warehouse combination (InItemSite) at the top of this list regardless of its pick priority.
I believe you should override this method to put the needed location to the top of the list instead of (or ahead of) the default issue location. Here is the code of this method of the SOShipmentEntry class for reference:
protected virtual void ResortStockForShipmentByDefaultItemLocation(SOShipLine newline, List<PXResult> resultset)
if (INSite.PK.Find(this, newline.SiteID)?.UseItemDefaultLocationForPicking != true)
return;
var dfltShipLocationID = INItemSite.PK.Find(this, newline.InventoryID, newline.SiteID)?.DfltShipLocationID;
if (dfltShipLocationID == null)
return;
var listOrderedByDfltShipLocationID = resultset.OrderByDescending(
r => PXResult.Unwrap<INLocation>(r).LocationID == dfltShipLocationID).ToList();
resultset.Clear();
resultset.AddRange(listOrderedByDfltShipLocationID);
}
Important! If we are talking about 21R2 version, there is the "Project-Specific Inventory" (materialManagement) feature which has its own extension of the SOShipmentEntry where some of the shipment creation methods (including the SelectLocationStatus) are overridden. The ResortStockForShipmentByDefaultItemLocation is not overridden, but if the customer uses this feature, I suggest to extend this extension rather than base SOSHipmentEntry:
namespace PX.Objects.PM.MaterialManagement
{
public class SOShipmentEntryMaterialExt : PXGraphExtension<SOShipmentEntry>

Related

In acumatica, how can I make changes in the Cost tab show up in the Revenue tab?

For example, if I change the "Original Budgeted Quantity" field to 9.00 (see first image below), I would like that to also change in the Revenue tab (see second image) without having to change the same thing twice.
Is there a way I can achieve this?
Cost Tab
Revenue Tab
Here are the details of the two fields I have highlighted in the images above:
Cost Tab Field
Revenue Tab Field
Let me know if I need to clarify anything or provide more information :)
Unfortunately I'm not very familiar with the Project Entry screen, and after a quick glance I couldn't find an easy way to tie the records from the Cost Budget tab to the Revenue Budget tab.
If you are planning on tying these two records together you may need some additional customization work to create the link that you are looking for if it doesn't already exist.
As far as the update itself, that is pretty straightforward and can be accomplished with a simple event handler, as demonstrated below.
namespace MyCompany.MyCustomization
{
public class ProjectEntryExtMyCustomization : PXGraphExtension<ProjectEntry>
{
public static bool IsActive() => true;
#region Actions
#endregion
#region Events
protected virtual void _(Events.FieldUpdated<PMCostBudget, PMCostBudget.qty> eventHandler)
{
PMCostBudget row = eventHandler.Row;
if (row is null) return;
// Replace the stub below with your PMRevenueBudget lookup
// using the link that you have defined.
PMRevenueBudget revenueDetail = new PMRevenueBudget();
// Assigns the PMRevenueBudget Qty field to match the PMCostBudget new value.
revenueDetail.Qty = eventHandler.NewValue as decimal?;
// Updates the PMRevenueBudget record in the cache.
Base.RevenueBudget.Update(revenueDetail);
}
#endregion
}
}
Don't forget to replace the line where I create a new PMRevenueBudget with the logic that you need for looking up the linked PMRevenueBudget record that you need. Let me know if you have any questions.

How to add a column to the grid that shows value from another screen in Acumatica?

I'm new to Acumatica, could you please help me? I have too screens IN202500 (stock items) and SO301000(sales orders). I added a field to stock items and now I need to show a value from that field in grid column of sale orders for each stock items. I suppose that I need to use PXDefault attribute for this?
There are a number of ways you can do this. I'll provide 3 possibilities.
If your View used by the grid contains InventoryItem, you may be able simply to select your custom field from InventoryItem and add it directly to the screen. I'll assume this is not an option or you likely would have found it already.
Create a custom field in a DAC extension on SOLine where you add your custom field as unbound (PXString, not PXDBString) and then use PXDBScalar or PXFormula to populate it. I haven't used PXDBScalar or PXFormula to retrieve a value from a DAC Extension, so I'll leave it to you to research. I do know this is super easy if you were pulling a value directly from InventoryItem, so worth doing the research.
Create as an unbound field as in #2, but populate it in the SOLine_RowSelecting event. This is similar to JvD's suggestion, but I'd go with RowSelecting because it is the point where the cache data is being built. RowSelected should be reserved, in general, for controlling the UI experience once the record is already in the cache. Keep in mind that this will require using a new PXConnectionScope, as Acuminator will advise and help you add. (Shown in example.) In a pinch, this is how I would do it if I don't have time to sort out the generally simpler solution provided as option 2.
Code for Option 3:
#region SOLine_RowSelecting
protected virtual void _(Events.RowSelecting<SOLine> e)
{
SOLine row = (SOLine)e.Row;
if (row == null)
{
return;
}
using (new PXConnectionScope())
{
SOLineExt rowExt = row.GetExtension<SOLineExt>();
InventoryItem item = SelectFrom<InventoryItem>
.Where<InventoryItem.inventoryID.IsEqual<#P.AsInt>>
.View.Select(Base, row.InventoryID);
InventoryItemExt itemExt = item.GetExtension<InventoryItemExt>();
rowExt.UsrSSMyDatAField = itemExt.UsrSSMyDataField;
}
}
#endregion

How can i with Axon re-initialize some value with the same first InitAvailableQuantityCommand?

assume we have an stock. this stock should persist product id and available quantity. the user of this stock can frequently update(InitAvailableQuantityCommand) available quantity.
if some product has been sold, our system will get a soldEvent(DecreaseAvailableQuantityCommand) and available quantity for sold product should be decress.
it works well with aggregate below, until one thing,
if i try again to re-initialize stock with InitAvailableQuantityCommand, the event will be ignored and an error is thrown
An event for aggregate [3333] at sequence [0] was already inserted"
What i try to achive is following:
InitAvailableQuantityCommand (productId =1, quantity = 10)
DecreaseAvailableQuantityCommand (productId =1, quantity = 1)
DecreaseAvailableQuantityCommand (productId =1, quantity = 1)
now hier we have 8 available products more.
and it this moment user will re-initialize stock with 20 available products for productId 1. the user will send a new InitAvailableQuantityCommand (productId =1, quantity = 20) and it this moment it fail and doesn't work.
What do i wrong?
thx.
#NoArgsConstructor
#Aggregate
#Data
public class AvailableQuantityAggregate {
private String partnerId;
private String productId;
#AggregateIdentifier
private String productVariationId;
private int quantity;
#CommandHandler
public AvailableQuantityAggregate(InitAvailableQuantityCommand cmd) {
final ApplyMore apply = AggregateLifecycle.apply(AvailableQuantityInitializedEvent.builder()
.partnerId(cmd.getPartnerId())
.productId(cmd.getProductId())
.productVariationId(cmd.getProductVariationId())
.quantity(cmd.getQuantity())
.build());
}
#CommandHandler
public void handle(DecreaseAvailableQuantityCommand cmd) {
AggregateLifecycle.apply(AvailableQuantityDecreasedEvent.builder()
.productVariationId(cmd.getProductVariationId())
.quantity(cmd.getQuantity())
.build());
}
#EventSourcingHandler
protected void on(AvailableQuantityInitializedEvent event) {
this.productVariationId = event.getProductVariationId();
this.partnerId = event.getPartnerId();
this.productId = event.getProductId();
this.quantity = event.getQuantity();
}
#EventSourcingHandler
protected void on(AvailableQuantityDecreasedEvent event) {
this.quantity = this.quantity-event.getQuantity();
}
}
The InitAvailableQuantityCommand instantiates an Aggregate. Aggregates inherently have identity. As such, the Aggregate Identifier is in place to denote whom/what it is. When you are event sourcing, which you are by default in Axon, the Event Store will ensure that you will not add events with the same aggregate id and sequence number. When you are publishing the InitAvailableQuantityCommand a second time however, you are telling the framework to publish an event with the same aggregate id and sequence number.
Hence, your modelling solution should be a little different. The action (aka, the command) of instantiating the aggregate is different from resetting it. Thus I'd suggest to add a different command to reset your aggregate to it's initial values.
Judging from your code snippet, the InitAvailableQuantityCommand is handled by a constructor. This means that Axon expects to need to create a new instance of an aggregate. But as you are expecting to load an instance, there is a collision of identifiers (fortunately).
What you'd need to do, is create a different command that contains the same information, but is handled by an instance method. This might be what you want to do anyway, because there a conceptual/functional different between first-time initialization, and "resetting".
In future versions of Axon, we will support "create-or-update" kind functionality, where a single Command could fulfill both roles.

What is the proper way to update values of DAC's retrieved via PXResultset?

We have a business requirement to set the SO return COST to the original cost issued without invoicing if possible. We determined that Sales Orders are necessary to track issuing materials to our client, and we are cost driven rather than price driven. We use FIFO costing, but SO return orders do not seem to return at the original COST unless invoiced (which we also don't do in a traditional manner).
I found that setting the unit/ext cost on the SO Shipment Line directly in the database before Confirm Shipment and Update IN appears to provide the results desired. Applying a custom menu option to streamline and strongly control the return, I cloned nearby code as a base. The section between the === is where I set the unit/ext cost. The PXTrace shows the expected value, but it is coming out as $0 on the shipment record. I thought I might need "docgraph.Update(sOShipmentLine)" to save it, but that's not accessible in this scope.
using (var ts = new PXTransactionScope())
{
PXTimeStampScope.SetRecordComesFirst(typeof(SOOrder), true);
//Reminder - SOShipmentEntry docgraph = PXGraph.CreateInstance<SOShipmentEntry>();
docgraph.CreateShipment(order, SiteID, filter.ShipDate, adapter.MassProcess, SOOperation.Receipt, created, adapter.QuickProcessFlow);
PXTrace.WriteError("Setting Cost");
//Set Cost on Shipment to Cost On SO Line
PXResultset<SOShipment> results =
PXSelectJoin<SOShipment,
InnerJoin <SOShipLine, On<SOShipLine.shipmentNbr, Equal<SOShipment.shipmentNbr>>,
InnerJoin <SOLine, On<SOLine.orderType, Equal<SOShipLine.origOrderType>,
And<SOLine.orderNbr, Equal<SOShipLine.origOrderNbr>, And<SOLine.lineNbr, Equal<SOShipLine.origLineNbr>>>>
>>,
Where<SOShipment.shipmentNbr, Equal<Required<SOShipment.shipmentNbr>>>>
.Select(docgraph, docgraph.Document.Current.ShipmentNbr);
PXTrace.WriteError("Shipment {0} - Records {1}", docgraph.Document.Current.ShipmentNbr, results.Count);
foreach (PXResult<SOShipment, SOShipLine, SOLine> record in results)
{
SOShipment shipment = (SOShipment)record;
SOShipLine shipmentLine = (SOShipLine)record;
SOLine sOLine = (SOLine)record;
==============================================
shipmentLine.UnitCost = GetReturnUnitCost(sOLine.OrigOrderType, sOLine.OrigOrderNbr, sOLine.OrigLineNbr, sOLine.CuryInfoID);
shipmentLine.ExtCost = shipmentLine.Qty * shipmentLine.UnitCost;
PXTrace.WriteError(string.Format("{0} {1}-{2} = {3} / {4}", shipmentLine.LineType, shipmentLine.ShipmentNbr, shipmentLine.LineNbr, shipmentLine.Qty, shipmentLine.UnitCost));
==============================================
}
PXAutomation.CompleteSimple(docgraph.Document.View);
var items = new List<object> { order };
PXAutomation.RemovePersisted(docgraph, typeof(SOOrder), items);
PXAutomation.RemoveProcessing(docgraph, typeof(SOOrder), items);
ts.Complete();
}
Still on the learning curve so I'm expecting the solution is likely simple and obvious to someone more experienced.
There's three phase to it:
Changing the value
Updating the cache
Persisting the cache
I think you are changing the value but not persisting it. The reason why it works after invoking Confirm Shipment or Update IN action is probably that these actions will persist all changes by calling the graph Save action.
To change a field value in a data view you would do:
DACRecord.Field = value;
DataView.Update(DACRecord);
The particularity of your example is that the request is not bound to a data view.
When you have a loose BQL request you can do the same operation with a cache object. In your example the Caches context is available from docGraph:
DACRecord.Field = value;
graph.Caches[typeof(DACType)].Update(DACRecord);
graph.Caches[typeof(DACType)].Persist(DACRecord, PXDBOperation.Update);
Update and Persist are often omitted because in many scenarios they will be called later on by other framework mechanism. For example if you were to do only Update on a UI field, the record won't be persisted until the user clicks on the save button.
Updating value on UI is a bit different than updating in cache.
The recommended approach for UI fields is to use SetValue:
cache.SetValue<DAC.DacField>(DACRecord, fieldValue);
Or use SetValueExt when you want to trigger the framework events like FieldUpdated when changing the field value:
cache.SetValueExt<DAC.DacField>(DACRecord, fieldValue);
You'll still have to update and persist the changes in cache for these too if you want the changes to stick without requiring the user to manually save the document.

How do I prevent SOLineSplit and INItemPlan from deleting when Qty = 0?

I noticed that when the quantity of a SOLine is zero, there are no SOLineSplit or INItemPlan records available for that line. The second the quantity is greater than 0, the system makes those records, and if the qty is set back to 0, the records are deleted.
Is there a way to prevent the SOLineSplit and INItemPlan objects from deleting when a record is set to 0 quantity?
Is there a way to still have the system create an SOLineSplit and INItemPlan if the SOLine is initially created with a 0 quantity?
The reason for the question is that a customer wants the system to lock the SOLine after a certain point, but also allow for the Qty to be adjusted from another screen. Since this is not directly changing the value on the SOLine screen, this isn't triggering the events to create the split and plan.
I have tried creating an instance of SOOrderEntry in the custom screen as follows:
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
//Also tried graph.Transactions.Current = line, but did not work
graph.Transactions.Update(line);
graph.Actions.PressSave();
Doing that keeps resulting in a null object reference error:
Error: An error occurred during processing of the field OrderQty : Object reference not set to an instance of an object..
System.NullReferenceException: Object reference not set to an instance of an object.
at PX.Objects.SO.SOOrderEntry.SOLine_OrderQty_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e) ...
I think the problem with your statement in using SOOrderEntry is you are not truly loading the order in the graph to update it correctly. After you create the graph instance and before the transaction update, you should load the document header like this...
SOOrderEntry graph = PXGraph.CreateInstance<SOOrderEntry>();
graph.Document.Current = graph.Document.Search<SOOrder.orderNbr>(line.OrderNbr, line.OrderType);
if(graph.Document.Current == null)
{
return;
}
graph.Transactions.Update(line);
graph.Actions.PressSave();
As for controlling how the plan and split records are entered from SOOrderEntry... The entries are controlled through the attributes on PlanID. The cache attached is where this gets added on SOOrderEntry...
[PXMergeAttributes(Method = MergeMethod.Append)]
[SOLineSplitPlanID(typeof(SOOrder.noteID), typeof(SOOrder.hold), typeof(SOOrder.orderDate))]
protected virtual void SOLineSplit_PlanID_CacheAttached(PXCache sender)
{
}
You can make your own graph extension of Sales order and replace the attribute with your own version of SOLineSplitPlanID... it might be a battle for you as I am not sure why you would want the plan record to exist when zero qty to plan.

Resources