With some help and a lot of trial and error I was able to create a customization that allowed for the following:
"Total Profit" field on the Sales Order summary area
"GP %" field on the Sales Order summary area
"Total Profit" field on the Sales Order details area (by line)
"GP %" field on the Sales Order details area (by line)
Total Profit = Ext. Price - Ext. Cost
GP % = (Total Profit / Ext. Price) * 100
The calculation for these four fields works some of the time, but I have noticed when I commit the line either by entering a new line or with Control + Enter, and then go back and change the Ext. Cost, the Total Profit and GP% don't update. Even when I save and refresh it does this. The only time it seems to work 100% is when I enter it in very methodically going one step at a time. But this is not how the out of the box totals are calculated. It doesn't matter which order you enter in your quantity, price, etc. - the out of the box totals always update. How do I make my four custom fields like this in Acumatica?
SOLine Custom fields (each field separated by a blank line):
[PXDBCurrency(typeof(SOLine.curyInfoID), typeof(SOLineExt.usrTotalProfit))]
[PXUIField(DisplayName = "Total Profit", Enabled = false)]
[PXFormula(typeof(Sub<SOLine.curyLineAmt, SOLine.curyExtCost>))]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXFormula(null, typeof(SumCalc<SOOrderExt.usrCuryOrderTotalProfit>))]
[PXDBDecimal(4)]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(Switch<Case<Where<SOLine.curyLineAmt, Equal<decimal0>>, decimal0>, Div<SOLineExt.usrTotalProfit, SOLine.curyLineAmt>>))]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXUIField(DisplayName = "GP %", Enabled = false)]
[PXFormula(typeof(Mult<SOLineExt.usrGPPercent, decimal100>))]
[PXDefault(TypeCode.Decimal, "0.0")]
SOOrder Custom fields (each field separated by a blank line):
[PXDBCurrency(typeof(SOOrder.curyInfoID), typeof(SOOrderExt.usrOrderTotalProfit))]
[PXUIField(DisplayName = "Total Profit", Enabled = false)]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXDBDecimal(4)]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXFormula(typeof(Switch<Case<Where<SOOrder.curyOrderTotal, Equal<decimal0>>, decimal0>, Div<SOOrderExt.usrOrderTotalProfit, SOOrder.curyOrderTotal>>))]
[PXDefault(TypeCode.Decimal, "0.0")]
[PXUIField(DisplayName = "GP %", Enabled = false)]
[PXFormula(typeof(Mult<SOOrderExt.usrOrderGPPercent, decimal100>))]
PXFormula should have no problems with DAC extensions and custom fields. It's really hard to tell what the issue is by looking only at field attributes. Please, at least, update your code snippet with DAC field declarations, so it becomes more clear what attributes belong to what field.
Related
I have a table ORDERS which contains column ORDER_DATE. I have created a Chart as a Bar type. I want the chart to show the amount of orders in a given date or range.
I'm following this Youtube tutorial which shows how to create a datepicker that returns a range in a Report. I'm trying to replicate this in a chart.
What I've done so far
Created datepicker items P5_DATE_FROM and P5_DATE_TO
Changed the Series Source Type to SQL Query
select ORDER_ID,
ORDER_NUMBER,
TO_CHAR(ORDER_DATE, 'YYYY-MM-DD') AS ORDERDATE,
STORE_ID,
FULL_NAME,
EMAIL,
CITY,
STATE,
ZIP_CODE,
CREDIT_CARD,
ORDER_ITEMS,
APEX$SYNC_STEP_STATIC_ID,
APEX$ROW_SYNC_TIMESTAMP
from ORDERS_LOCAL
where TO_CHAR(ORDER_DATE, 'YYYY-MM-DD') between :P5_DATE_FROM and :P5_DATE_TO
Source Page Items to Submit added P5_DATE_FROM,P5_DATE_TO
I basically followed the exact steps of the video. However, since the video is for a report and mine is for a chart, the chart isn't actually returning any data.
I think this is because for charts there are additional fields I need to configure. I noticed the video didn't cover Column Mapping and I'm a bit confused to what to enter here.
----------------UPDATE-------------
I've followed Koen's instructions from his answer. It seems like to TO_CHAR function was causing the error. I've updated the SQL Query to below:
select ORDER_ID,
ORDER_NUMBER,
ORDER_DATE,
STORE_ID,
FULL_NAME,
EMAIL,
CITY,
STATE,
ZIP_CODE,
CREDIT_CARD,
ORDER_ITEMS,
APEX$SYNC_STEP_STATIC_ID,
APEX$ROW_SYNC_TIMESTAMP
from ORDERS_LOCAL
where ORDER_DATE between TO_DATE(:P5_DATE_FROM,'YYYY-MM-DD') and TO_DATE(:P5_DATE_TO,'YYYY-MM-DD')
However, on the Page Designer I cannot save and run page until I select Column Mapping - Label and Value. I've set the Label as ORDER_DATE but I am unsure of what to select for the Value.
Setting the Value to ORDER_DATE shows an error Ajax call returned server error ORA-01403: no data found for CHART Count Orders by Date.
and selecting any other Value such as ORDER_NUMBER or ZIP_CODE populates the chart with the actual integer value of the column (ex: ZIP_CODE returns a chart of x-axis: date, y-axis: actual zip code numbers)
I see 2 possible issues.
You submit P5_DATE_FROM and P5_DATE_TO but the source has P1_DATE_FROM and P1_DATE_TO - not sure that is a typo but it should all be the same...
The where clause is wrong. This
from ORDERS_LOCAL
where TO_CHAR(ORDER_DATE, 'MM-DD-YYYY') between :P1_DATE_FROM and :P1_DATE_TO
should be
from ORDERS_LOCAL
where ORDER_DATE between TO_DATE(:P1_DATE_FROM,'MM-DD-YYYY') and TO_DATE(:P1_DATE_TO,'MM-DD-YYYY')
Reason: the column orders_local.order_date is of datatype DATE. If you convert it to a string using TO_CHAR then it will be a string comparison, not a date comparison.
Note that bind variables P1_DATE_FROM and P1_DATE_TO are strings - they're defined in the DOM on the html page and that has no concept of oracle datatypes so everything is just a string. If they're used as such in a date comparison, you're relying on implicit conversion by the database. It's safer to do explicit conversion using the TO_DATE function.
--UPDATE--
Your question starts with "I want the chart to show the amount of orders in a given date or range.". Well... there is your answer. The "Value" is the amount of orders on each day. Update your source query to include a count of the orders and group by day. Then make your value attribute the column that has the count.
This question is a follow up to another SO question
I want a bar chart to show the amount of orders in a given date or range. Koen's updated answer shows:
'The "Value" is the amount of orders on each day. Update your source query to include a count of the orders and group by day. Then make your value attribute the column that has the count.'
How would I go about doing this?
Summary: I have a table ORDERS which contains column ORDER_DATE. I have created a Chart as a Bar type. I want the chart to show the amount of orders in a given date or range.
I'm following this Youtube tutorial which shows how to create a datepicker that returns a range in a Report. I'm trying to replicate this in a chart.
What I've done so far
Created datepicker items P5_DATE_FROM and P5_DATE_TO
Changed the Series Source Type to SQL Query
select ORDER_ID,
ORDER_NUMBER,
ORDER_DATE,
STORE_ID,
FULL_NAME,
EMAIL,
CITY,
STATE,
ZIP_CODE,
CREDIT_CARD,
ORDER_ITEMS,
APEX$SYNC_STEP_STATIC_ID,
APEX$ROW_SYNC_TIMESTAMP
from ORDERS_LOCAL
where ORDER_DATE between TO_DATE(:P5_DATE_FROM,'YYYY-MM-DD') and TO_DATE(:P5_DATE_TO,'YYYY-MM-DD')
Source Page Items to Submit added P5_DATE_FROM,P5_DATE_TO
I basically followed the exact steps of the video.
However, on the Page Designer I cannot save and run page until I select Column Mapping - Label and Value. I've set the Label as ORDER_DATE but I am unsure of what to select for the Value.
Setting the Value to ORDER_DATE shows an error Ajax call returned server error ORA-01403: no data found for CHART Count Orders by Date.
and selecting any other Value such as ORDER_NUMBER or ZIP_CODE populates the chart with the actual integer value of the column (ex: ZIP_CODE returns a chart of x-axis: date, y-axis: actual zip code numbers)
-----------UPDATE----------
Per Koen's answer, I've updated the Source SQL Query to below but I am now receiving an error.
And if I copy the exact query that Koen suggested, I run into below:
I did some messing around and found if I include APEX$SYNC_STEP_STATIC_ID and APEX$ROW_SYNC_TIMESTAMP the missing expression error goes away but instead I receive the GROUP BY error.
Something like this (untested) should work. Use TRUNC to ensure all orders on the same date are grouped (since date has a time portion, you'd have a different column for each different date time. Use ORDER_DATE as label and ORDER_COUNT as value.
select COUNT(ORDER_ID) AS ORDER_COUNT,
TRUNC(ORDER_DATE) AS ORDER_DATE,
from ORDERS_LOCAL
where ORDER_DATE between TO_DATE(:P5_DATE_FROM,'YYYY-MM-DD') and TO_DATE(:P5_DATE_TO,'YYYY-MM-DD')
GROUP BY TRUNC(ORDER_DATE)
I have a column called "Normalize Data" which has 2 values "yes" and "no".
Requirement:
When the user clicks on "yes", I want my measure(called Prev_YR_Trans) to do one thing, but if the user clicks "no" I want the measure to do another thing.
Let me explain with an example - My measure Prev_YR_Trans displays the transactions for the previous time period. Let's the say current time period is "25", then it will display the transactions for time period "24". Now, if the user clicks on "Yes", then I want the previous year's transactions to get "normalized" i.e. I want it to get multiplied by the variance b/w the 2 time periods.
What I have tried:
Prev_YR_Trans =
#getting the current & previous time period from Table "X"
VAR prevSeason = CALCULATE(MAX('X'[Time Period]))-1
VAR maxSeason = CALCULATE(MAX('X'[Time Period]))
#getting variance b/w prev and current time periods
VAR maxSeason_footfall = CALCULATE(SUM('Y'[Park_Footfall]),'Y'[Time Period]=maxSeason)
VAR prevSeason_footfall = CALCULATE(SUM('Y'[Park_Footfall]),'Y'[Time Period]=prevSeason)
VAR footfall_variance = 1+((maxSeason_footfall-prevSeason_footfall)/prevSeason_footfall)
#trying to get the option that the user clicks on(?)
VAR bb = CALCULATE(('X'[Normalize data]))
#returns normalized numbers if user chooses "Yes" else returns actual numbers
RETURN
IF(bb="Yes",
CALCULATE(SUM('X'[Receipt Count]),'X'[Time Period]= prevSeason)*footfall_variance,
CALCULATE(SUM('X'[Receipt Count]),'X'[Time Period]= prevSeason)
)
In my above measure, the "VAR bb = CALCULATE(('X'[Normalize data]))" is giving me an error as it needs some aggregation like max,min,sum,etc.
How do I resolve my measure so that it displays the correct numbers?
Edit (Solved) More on "Normalize Data" column:
I've solved the "Normalize Data" column by creating a new table called "Normalize Slicer" with yes/no values. Here is the link
Also, if you could help me out with my "Normalize Data" column too then that would great!
So with the "Normalize Data" column - I just want it to display 2 options "Yes" and "No" but I realized I need to create it as a column for the slicer functionality to work.
So for my formula I'd like to display "Yes" if the "Time Period" is less than or equal to the previous time period and "No" otherwise.
I tried to do the following:
Normalize data = IF('X'[Time Period]<=(CALCULATE(MAX('X'[Time Period]))-1),"Yes","No")
But above column just displays "No" for all the values so as a workaround I manually entered the previous time period in the formula:
Normalize data = IF('X'[Time Period]<=24,"Yes","No")
The above formula does work but I'd like it to be dynamic and not a manually entered value.
Insted of
VAR bb = CALCULATE(('X'[Normalize data]))
Use this:
var bb = SELECTEDVALUE('X'[Normalize data])
https://dax.guide/selectedvalue/
I have cloudwatch entries that may be group with respect to a certain field. To be clear assume that field is city. I would like to count the entries with respect to cities. This is the easy part.
fields city
|stats count(*) by city
However I also want to get maximum minimum and average of this count, but I can not. Is it possible to have such queries i.e:
fields city
|stats avg(count(*) by city)
The console return an error for such query : mismatched input 'by' expecting {SYM_COMMA, SYM_RParen}
Here's how you'd do it: You'd first get the count (that you already figured) and then get the metrics you want by calling the relevant functions like so:
fields city
|stats count(*) as cityCount, avg(cityCount), max(cityCount), min(cityCount)
I have recently modified the default report 207 Sales Invoice and created a custom report. One request I had, was to display Shipment information on this invoice for each shipment that the current invoice covers. Particularly I needed to show the following values:
Shipment No. from the Delivery Note
Quantity of the Shipment
The standard report only shows the posted shipment date and the quantity and that only for one shipment (I believe its the last one). It does not show the "Shipment No.".
When making the required changes I had several problems and it was difficult to find any information about the findPostedShipmentDate() function, so I have decided to post my solution here.
The question would be:
How can I show multiple shipments on the sales invoice?
How can I show the "Shipmente No." and the individual quantities or each shipment?
In the function "Sales Invoice Line"::OnAfterGetRecord() the function findPostedShipmentDate() is called. This function performs several checks to find the posting date and if necessary calls the function GenerateBufferFromValueEntry() which fills the table "SalesShipmentBuffer" which temporary stores all the shipment information for the current "Sales Invoice Line" record:
CASE "Sales Invoice Line".Type OF
"Sales Invoice Line".Type::Item:
GenerateBufferFromValueEntry("Sales Invoice Line");
...
Afterwards it performs several checks on the buffer table and deletes the entries again (more on this later).
In the GenerateBufferFromValueEntry the following happens. The actual shipment information is stored in the ItemLedgerEntry (32) table, in order to find the correct rows, the ValueEntry (5802) table is used, which contains the right key (ValueEntry."Item Ledger Entry No.").
Additionally to the existing range filters I needed to add one line to limit the range to the Item No. and I had to remove the range filter for the Entry No.. I also modified the function AddBufferEntry to additionally take the ItemLedgerEntry."Document No." which contains the Shipment No. from the delivery note. Here is the complete code:
TotalQuantity := SalesInvoiceLine2."Quantity (Base)";
ValueEntry.SETCURRENTKEY("Document No.");
ValueEntry.SETRANGE("Document No.",SalesInvoiceLine2."Document No.");
ValueEntry.SETRANGE("Posting Date","Sales Invoice Header"."Posting Date");
ValueEntry.SETRANGE(ValueEntry."Item No.", "Sales Invoice Line"."No."); //Added
ValueEntry.SETRANGE("Item Charge No.",'');
//ValueEntry.SETFILTER("Entry No.",'%1..',FirstValueEntryNo); //Removed
IF ValueEntry.FIND('-') THEN BEGIN
REPEAT
IF ItemLedgerEntry.GET(ValueEntry."Item Ledger Entry No.") THEN BEGIN
IF SalesInvoiceLine2."Qty. per Unit of Measure" <> 0 THEN
Quantity := ValueEntry."Invoiced Quantity" /
SalesInvoiceLine2."Qty. per Unit of Measure"
ELSE
Quantity := ValueEntry."Invoiced Quantity";
AddBufferEntry(
SalesInvoiceLine2,
-Quantity,
ItemLedgerEntry."Posting Date",
ItemLedgerEntry."Document No."); //Added
TotalQuantity := TotalQuantity + ValueEntry."Invoiced Quantity";
END;
FirstValueEntryNo := ValueEntry."Entry No." + 1;
UNTIL (ValueEntry.NEXT = 0) OR (TotalQuantity = 0);
END;
In the FindPostedShipmentDate function I also deleted the first couple of lines to prevent the function from exiting before the SalesShipmentBuffer had been filled:
IF "Sales Invoice Line"."Shipment No." <> '' THEN
IF SalesShipmentHeader.GET("Sales Invoice Line"."Shipment No.") THEN
EXIT(SalesShipmentHeader."Posting Date");
IF "Sales Invoice Header"."Order No."='' THEN
EXIT("Sales Invoice Header"."Posting Date");
In the last part of FindPostedShipmentDate the records of SalesShipmentBuffer are deleted again, which might seem a bit strange at first (it did to me). The reason is that even after the DELETE and DELETEALL functions are called, the record variable retains the values that were stored in the record before calling these functions. So the result is that the SalesShipmentBuffer tables is cleared, such that its empty for the next run of the findPostedShipmentDate() function, but the values can still be used in the report. The other confusing part is that actually does a very simple thing which is made to look very complicated. If the buffer contains only one line it deletes the line, otherwise if it contains more than one line it first calculates the sum of all quantity fields and then deletes all lines.
Anyway, I only had to make one change, which was removing the following line since the Document No. field now contains the Shipment No. and not the Invoice No.
SalesShipmentBuffer.SETRANGE("Document No.","Sales Invoice Line"."Document No.");
Here is the remaining code:
SalesShipmentBuffer.SETRANGE("Line No." ,"Sales Invoice Line"."Line No.");
SalesShipmentBuffer.SETRANGE("No." ,"Sales Invoice Line"."No.");
IF SalesShipmentBuffer.FIND('-') THEN BEGIN
SalesShipmentBuffer2 := SalesShipmentBuffer;
IF SalesShipmentBuffer.NEXT = 0 THEN BEGIN
SalesShipmentBuffer.GET(
SalesShipmentBuffer2."Document No.",
SalesShipmentBuffer2."Line No.",
SalesShipmentBuffer2."Entry No."
);
SalesShipmentBuffer.DELETE;
EXIT(SalesShipmentBuffer2."Posting Date");
END ;
SalesShipmentBuffer.CALCSUMS(Quantity);
IF SalesShipmentBuffer.Quantity <> "Sales Invoice Line".Quantity THEN BEGIN
SalesShipmentBuffer.DELETEALL;
EXIT("Sales Invoice Header"."Posting Date");
END;
END ELSE
EXIT("Sales Invoice Header"."Posting Date");
That's it. Now I just had to modify the DataItem source entries to show the necessary fields on the report.
The changes I had to make are actually not so many, but the code is not documented at all and I could not find much information when I googled for this problem or the functions involved. So I hope my post is helpful, in case someone has a similar problem than I had.
On a side not, I do not understand why the above code segment is written in such a wired why, I belive (though I have not verified this), that it could be simplified into the following code, which is much more readable. Please leave comments if you feel that my analysis is wrong.
SalesShipmentBuffer.SETRANGE("Line No." ,"Sales Invoice Line"."Line No.");
SalesShipmentBuffer.SETRANGE("No." ,"Sales Invoice Line"."No.");
SalesShipmentBuffer.FINDFIRST;
IF SalesShipmentBuffer.COUNT > 0 THEN BEGIN
IF SalesShipmentBuffer.COUNT = 1 THEN BEGIN
SalesShipmentBuffer.DELETE;
EXIT(SalesShipmentBuffer2."Posting Date");
END ELSE BEGIN
SalesShipmentBuffer.CALCSUMS(Quantity);
SalesShipmentBuffer.DELETEALL;
EXIT("Sales Invoice Header"."Posting Date");
END;
END ELSE
EXIT("Sales Invoice Header"."Posting Date");