Thursday, July 2, 2015

Calling Oracle forms documents form non EBS applications, i.e. OBIEE

Few days ago my colleague while browsing through OBIEE dashboards commented that it would be nice to be able to  click on an invoice on a detailed view and open the actual document.

Well, there is a way to do just that. 

Oracle has a nice document "Integrating Oracle Business Intelligence Applications with Oracle E-Business Suite (Doc ID 555254.1)"

It says:
"In order to generate an Action Link you will first have to do the following:
  1. Identify the Oracle E-Business Suite Application page/function that you want to link to. Obtain the function_id of that page and identify the querystring parameters required by that page. This will have to be done by going through Oracle E-Business Suite documentation."
That's good to know, but ... there is no documentation on it.

In the same note they describe function 

FND_RUN_FUNCTION.get_run_function_url(form function id, resp appl id, resp id, sg id, form params)

Which generates URL to a specific document that can be opened from browser window. That link can be added as a hyperlink to a non web based application and clicking it will bring up the  specific document within EBS. This is a gold mine, and a way to improve efficiency of work, but how do I know how to pass the parameters?

The first parameter is the form function id. This can be retrieved by running fnd_function.get_function_id('function name'). 

Example:

Select fnd_function.get_function_id('AR_ARXTWMAI_HEADER') from dual

The function name can be found by going to the specific form and following the steps below:

The second and third parameters, resp appl id, resp id can be found by going to:

Help -> Diagnostics -> Examine
Block: $PROFILES$
Fields: RESP_APPL_ID and RESP_ID

The last parameter, form params, is the hardest one, and without it the form will open but will not query a specific record.  Doc ID 555254.1 says to find the values by "going through Oracle E-Business Suite documentation", but no documentation exists in that regard. When I asked Oracle Support about it they were not aware of any.

Here is how I worked around it.

When you open a specific document, go to File menu and see if you can place it in navigator


Most of the major documents have this feature available.

When you do that the form will appear in the Documents tab of the EBS navigator


By itself it is no very useful, but clicking on the document will open the particular invoice. This means that the navigator passes the proper parameters to the form to open it. 

To capture those parameters enable forms trace by using instructions  from note 438652.1 section 2.2.
Those instruction are very comprehensive but most of the time it is sufficient to find the site value of the profile ICX: Forms Launcher and copy it to user profile appended with "?record=collect"

Example: https://hostname.domain:port/OA_HTML/frmservlet?record=collect

After setting up the profile option exit Oracle and log back it again for it to take effect.
Go to the Document added to the Navigator then double click on it. 

When done, retrieve the debug file located in $FORMS_TRACE_DIR 
and look for string "other_params"

Here is what I found for the AR invoice

AR_ARXTWMAI_HEADER open_flag: Y session_flag: Y other_params: FP_CUSTOMER_TRX_ID=647666 

This not only tells me the form parameters but also the name of the function, so I could have skipped the step above to find the name. 

Here is what it is for Purchase Order

Executing function: PO_POXPOEPO open_flag: Y session_flag: Y other_params: PO_HEADER_ID= 213180 ACCESS_LEVEL_CODE="MODIFY" POXPOEPO_CALLING_FORM="DESKTOP" 

Now I have all the needed parameters I can call my function:

For AR Invoice:

 select FND_RUN_FUNCTION.get_run_function_url(fnd_function.get_function_id('AR_ARXTWMAI_HEADER'), 222, 20678, 0,'FP_CUSTOMER_TRX_ID='||CUSTOMER_TRX_ID) from RA_CUSTOMER_TRX_All

This query creates URL to every single customer invoice. You can copy and paste it into browser and it will open the invoice. If you are not logged in it will prompt you to log in before opening the document. You must have access to the responsibility or the document will produce no access error. 

Here is syntax for other sample documents, make sure to replace the resp_appl_id and resp_id before running it on your system:

-- Purchase Order(Purchasing Superuser)
select FND_RUN_FUNCTION.get_run_function_url(fnd_function.get_function_id('PO_POXPOEPO'), 201, 20707, 0, 'po_header_id='||po_header_id||' ACCESS_LEVEL_CODE=MODIFY POXPOEPO_CALLING_FORM=DESKTOP') from po_headers_all

Note that I am passing all 3 required parameters for PO.  'po_header_id='||po_header_id||' ACCESS_LEVEL_CODE=MODIFY POXPOEPO_CALLING_FORM=DESKTOP'

Passing just the PO_HEADER_ID will not open the record

-- Quick Sales Order (Order Management Superuser)
select FND_RUN_FUNCTION.get_run_function_url(fnd_function.get_function_id('ONT_OEXOETEL'),660, 21623, 0, 'desktop_header_id='||header_id) from oe_order_headers_all 

Note that the correct parameter is "'desktop_header_id" not "header_id" which is an existing and intuitive parameter but using it will not open the form

--AP Invoices (Payables Manager)
 select FND_RUN_FUNCTION.get_run_function_url(fnd_function.get_function_id('AP_APXINWKB'), 200, 20639, 0,'INVOICE_ID='||invoice_id) from
AP_INVOICES_ALL

Even when a particular function cannot be placed on navigator, it may be still possible to get direct link to it, but it is harder to determine the right parameters. They can be determined by opening the form in Forms Developer and reading them from the triggers.  
The Org items and Material Workbench are examples of this scenario.

--Org Items (Inventory)
select FND_RUN_FUNCTION.get_run_function_url(fnd_function.get_function_id('INV_INVIDITM_ORG'), 401,  20634, 0,'chart_of_accounts_id=50348 org_id='||mp.organization_id||' item_id='||inventory_item_id||' org_code='||mp.organization_code)
 from mtl_system_items_b msi , mtl_parameters mp where msi.organization_id=mp.organization_id

--Material Workbench for an item and org (Inventory)
select FND_RUN_FUNCTION.get_run_function_url(fnd_function.get_function_id('INV_INVMATWB'), 401,  20634, 0,
'ORGANIZATION_ID='||organization_id||' INVENTORY_ITEM_ID='||inventory_item_id||' OWNING_QUERY_MODE=1 G_QUERY_FIND=FALSE')
from mtl_onhand_quantities
-- skip the organization_id if you want availability across orgs 

--Material transactions for Sales Order Issue, for specific item that not older than 3 months and not newer than one month (Inventory). This is an interesting example as it involves dates

SELECT FND_RUN_FUNCTION.get_run_function_url (
          fnd_function.get_function_id ('INV_INVTVTXN'),
          401,
          20634,
          0,
             'MODULE=CST FORM_USAGE_MODE=DSLA_DRILLDOWN INVTVTXN_GO_DETAIL=Y ORG_ID=102 INVTVTXN_INVENTORY_ITEM_ID='
          || inventory_item_id
          || ' INVTVTXN_TRXN_TYPE_ID=33 INVTVTXN_FROM_DATE='
          || SUBSTR (fnd_date.date_to_canonical (SYSDATE - 90),
                     1,
                       INSTR (fnd_date.date_to_canonical (SYSDATE - 90),
                              ' ',
                              1,
                              1)
                     - 1)
          || ' INVTVTXN_TO_DATE='
          || SUBSTR (fnd_date.date_to_canonical (SYSDATE - 30),
                     1,
                       INSTR (fnd_date.date_to_canonical (SYSDATE - 30),
                              ' ',
                              1,
                              1)
                     - 1))
  FROM mtl_system_items_b
 WHERE organization_id = 102 AND segment1 = 'XFR0150R240R'


--WiP Jobs

SELECT FND_RUN_FUNCTION.get_run_function_url (
          fnd_function.get_function_id ('WIP_WIPDJMDF_DEFINE'),
          706, 20560,
          0,
             'GID="'
          || wip_entity_id
          || '" ORG_ID="'
          || organization_id
          || '"')

  FROM wip_discrete_jobs where trunc(creation_date)=trunc(sysdate)


Using those URLs you can integrate third party applications with EBS, but you can also use the same parameters to create personalizations to call one Oracle form another from using the "Launch a Function" builtin.

Here is a proof of concept, you can use variables instead of the fixed values but the principle remains the same.


Click on Apply Now and the item form will open


A problem with calling Oracle from from an URL is that if you already have EBS open the URL may hijack the current session and hard close all the documents on the screen while keeping them locked.

To avoid that use this syntax when calling the URL from a third application:

href="http://url_from_above_queries_here"
target="_blank" name="Option" value="-noframemerging"


Opening the URL with the -nonframemerging option in IE will force a new EBS window to open.



Sunday, May 17, 2015

Email override for concurrent request Delivery Options

R12 offers a great feature sending emails directly from Concurrent Requests using Delivery Options.
Gone are the days where one had to build a new workflow and create a role to email a document to external party. With BI publisher the emails can be very fancy containing colors, graphics end everything else as long as MS Word and HTML can take it.

But adding this new great feature oracle broke something else. The Workflow Email Override Address does not  apply to the Delivery Options and that means that one can send external emails form a cloned test instance. As you can imagine the consequences can be disastrous, i.e. we have a scheduled concurrent requests that email customers the AR invoices. Now if someone started testing AR invoices in the clone instance...

Some companies worked around the issue by blocking the email functionality on clone instances. That works but now one can't properly test the new workflow emails nor concurrent request emails. So there are 3 options:

- you can deploy new functionality related to workflow or concurrent request emailing but risk that critical documents such as invoices get emailed to customers from the cloned instance
- do not deploy any new functionality
- deploy in PROD but without complete test cycle

Surprisingly, Oracle DEV being the siloed organization they are, looked only at the Delivery Option side of the coin and recognized the need as an enhancement request #9308736. ERs are nice but they can take years to deploy, it is 2015 and and couple of  ERs I created in 2002 and 2004 were just recently released.

If waiting 10 or 15 years is not an option for your organization here is how you can fix the issue:


  • Create an email address where the string is equal to the SID of the test database and get access to the email box. It would have this format: TEST@yourcompanyname.com. Replace the values with proper SID and domain names
  • Add this script to the cloning process. The script needs to be incorporated as integral part of the cloning process and executed after the SID has been already changed form prod to the test instance, but before concurrent manager is brought up:
-- this script must be executed
-- after the NAME column has been set in V$DATABASE to the correct clone instance
-- but before concurrent managers get up

--This section sets the workflow email override address to
--be equal to the SID of the test instance @yourcompanyname.com

update fnd_svc_comp_param_vals set parameter_value=(select name from v$database)||'@'||'yourcompanyname.com'
where parameter_id =(select parameter_id from fnd_svc_comp_params_tl where display_name = 'Test Address' );

commit;

-- This section updates all the concurrent requestsincluding those that are scheduled or were being
-- executed when the instance was cloned

-- It updates the email address of every request to eb that of the workflow email override
  update  fnd_conc_pp_actions set argument3=
  ( select fscpv.parameter_value  -- go to workflow override email address
    from fnd_svc_comp_params_tl fscpt
    ,fnd_svc_comp_param_vals fscpv
    where fscpt.display_name = 'Test Address'
    and fscpt.parameter_id = fscpv.parameter_id),
    argument4=( select fscpv.parameter_value  -- go to workflow override email address
    from fnd_svc_comp_params_tl fscpt
    ,fnd_svc_comp_param_vals fscpv
    where fscpt.display_name = 'Test Address'
    and fscpt.parameter_id = fscpv.parameter_id),
    argument5=null,
    argument6=null,
    argument8=null,
    argument9=null,
    argument10=null
    where ACTION_TYPE =7 ;

commit;


  • Create trigger on the FND_CONC_PP_ACTIONS table to update any email specified by user during new request submission to the email override. This script needs to be executed at the same time as the previous one

CREATE OR REPLACE TRIGGER XX_EMAIL_OVERRIDE
BEFORE INSERT or UPDATE
   ON fnd_conc_pp_actions
  FOR EACH ROW
DECLARE

-- This trigger overrides send to  email address  on non production Oracle servers
v_override_email varchar2(100);
v_instance varchar2(100); -- name of the oracle instance 

BEGIN

select name 
into v_instance
 from v$database;
If v_instance!='PROD' -- name of production instance, replace as appropriate
-
 then 
 BEGIN

if :new.ACTION_TYPE =7 -- if the request output is going to be emailed in post processing
 --if :new.ARGUMENT1 ='E' -- if the request output is going to be emailed in post processing
 then
 select decode(fscpv.parameter_value,'NONE',null,fscpv.parameter_value)  -- go to workflow override email address
 into v_override_email 
    from fnd_svc_comp_params_tl fscpt 
    ,fnd_svc_comp_param_vals fscpv 
    where fscpt.display_name = 'Test Address' 
    and fscpt.parameter_id = fscpv.parameter_id;
    
        
   -- if v_override_email!='NONE' then -- if workflow override email is set

:new.ARGUMENT3:=v_override_email; --override sender address
:new.ARGUMENT4:=v_override_email;  -- send the email to the work from over
:new.ARGUMENT5:=null;--delete any addtional cc email addresses
:new.ARGUMENT6:=null;
:new.ARGUMENT7:=null;
:new.ARGUMENT8:=null;
:new.ARGUMENT9:=null;
:new.ARGUMENT10:=null;



--end if; -- end for override email is !='NONE'

end if; -- end of if the output is to be emailed 

EXCEPTION
   WHEN OTHERS
   then null;
   
END;

end if; -- end if the instance is not PROD

EXCEPTION
   WHEN OTHERS
   then null;
   
END;




Monday, January 19, 2015

Ship Sets on Internal and EDI orders

In some cases customers require their shipments to be complete rather than shipped piecemeal. In other cases it is efficient to organize picking work around picking and packing the complete delivery, rather than each line separately.

To address those needs you can use ship sets on Oracle OM lines. All lines belonging to the same ship set will not be released for picking until every single line in the set has full quantity available. In most organizations the largest shipments are replenishments to remote branches, but out of the box there is no way to add a ship set to a manually created internal requisition line or one released from the ASCP plan. This means that the largest shipments cannot be easily consolidated for picking and packing unless someone queries the internal order and adds the ship sets manually after they are created. This is mightily inconvenient if you ask me.

To remedy this problem one might be tempted to simply add a trigger on the OE_LINES_IFACE_ALL to add the ship set. Unfortunately this is is not that simple as single internal order my contain multiple ship sets. An internal order may ship from and to multiple locations and we don't want to pick and pack together items that will have to be dispatched to different destinations. Even within a single destinations we may have requirements to ship on different days and have to have different ship set for each day. There may be additional criteria such as shipping priority or shipping method that drives separate deliveries from a single order.

We can't evaluate all the lines on an order for those conditions before they are all completely inserted making a potential trigger on OE_LINES_IFACE_ALL useless.

Ideally we would put a trigger on OE_HEADERS_IFACE_ALL but the header is inserted before the lines...

Fortunately the Order Import concurrent job updates the OE_HEADERS_IFACE_ALL with the request_id, before even looking at the lines. This is an ideal time point to evaluate all the lines for ship set application, as at this point we know the lines are all complete and their number or data will not have any more inputs.

Here is sample code of an After Update trigger on OE_HEADERS_IFACE_ALL.  In addition to checking the dates and shipping warehouse, it checks if the customer (internal for IR/ISO or external for EDI) has ship sets enforced on the customer or site level. This allows putting tailored ship sets to some customers and none to others, depending on the setup preferences.

CREATE OR REPLACE TRIGGER XX_SHIP_SET_IFACE
AFTER UPDATE
   ON OE_HEADERS_IFACE_ALL
  FOR EACH ROW
DECLARE

-- This trigger is intended to create different ship sets per Request Date and warehouse for each order imported thru the interface
-- The update is in AFTER Update trigger since every time an order is procesed the OE_HEADERS_IFACE_ALL gets updated with the processing concurrent request ID.
-- At this point the lines are all completly in the OE_LINES_IFACE_ALL tabel and can be groupped into ship sets

cursor c is
  SELECT ol.ORIG_SYS_DOCUMENT_REF, ol.REQUEST_DATE, ol.SHIP_FROM_ORG_ID  -- internal order ship set grouping rules
    FROM OE_LINES_IFACE_ALL ol, po_requisition_lines_all prl
   WHERE ol.ORIG_SYS_DOCUMENT_REF =:new.ORIG_SYS_DOCUMENT_REF
   and order_source_id=10 -- internal orders
   and ol.ORIG_SYS_LINE_REF=to_char(prl.REQUISITION_LINE_ID)
 GROUP BY ol.ORIG_SYS_DOCUMENT_REF,
         ol.SHIP_SET_NAME,
         ol.SHIP_FROM_ORG_ID,
         ol.REQUEST_DATE,
         prl.attribute1
UNION all
  SELECT ol.ORIG_SYS_DOCUMENT_REF, ol.REQUEST_DATE, ol.SHIP_FROM_ORG_ID -- EDI orders
    FROM OE_LINES_IFACE_ALL ol
   WHERE ol.ORIG_SYS_DOCUMENT_REF =:new.ORIG_SYS_DOCUMENT_REF
   and order_source_id!=10
GROUP BY ol.ORIG_SYS_DOCUMENT_REF,
         ol.SHIP_SET_NAME,
         ol.SHIP_FROM_ORG_ID,
         ol.REQUEST_DATE
Order by REQUEST_DATE;

cursor ship_set is -- check if the ship sets are enforeced on site or customer level
select 0 from  dual where exists
(select 0 from
hz_cust_site_uses_all
where site_use_id =:new.ship_to_org_id
and SHIP_SETS_INCLUDE_LINES_FLAG='Y'
union all
select 0 from
 hz_cust_accounts_all
where CUST_ACCOUNT_ID =:new.sold_to_org_id
and SHIP_SETS_INCLUDE_LINES_FLAG='Y');


       
i number;

BEGIN
 i:=0;

 for z in ship_set loop --only for sites with enabled ship sets

 for r in c loop -- loop thru each combination of request date and warehouse within each order header

 i:=i+1;

 update OE_LINES_IFACE_ALL
 set SHIP_SET_NAME=to_char(i)
 where ORIG_SYS_DOCUMENT_REF=r.ORIG_SYS_DOCUMENT_REF
 and nvl(REQUEST_DATE, trunc(sysdate))=nvl(r.REQUEST_DATE, trunc(sysdate))
 and nvl(SHIP_FROM_ORG_ID,0)= nvl(r.SHIP_FROM_ORG_ID,0);


 end loop;

 end loop;


EXCEPTION
   WHEN OTHERS
   then null; -- if ship sets cannot be added the order is still good to go

END;
/