Monday, February 13, 2012

Updating EBS data from APEX using Triggers and APIs (3)

In the previous posts I showed how to call the EBS APIs in a Page Process of the APEX page.
Before that post I blogged about using views to query the EBS data. In this post we will do a combination of those techniques.

In APEX you can develop really fast; e.g. when you create a Form on table with Report, in less than a minute you have an Interactive Report where people can view and analyse the data in different ways. Clicking on the edit link in the report will allow you to update and delete (and create) the data.

By building your pages manually and creating the fetch and process data manually (as in the previous APIs posts), you lose a bit of productivity. By using updateable views you gain again some development speed. The updateable view technique, which means creating "INSTEAD OF" triggers on top of your views, is not specific for EBS, but you can use in any project (for example in 2006, I blogged about this feature when I was using it in DG Tournament).

When I tried to create a person or update the email address of a person through my updateable view I received following error:


Unless I missed a setting in EBS, it looks like the EBS API call I do in the trigger is not really "trigger compatible". When I discussed with Thierry and Paolo they told me about a parameter called p_validate (which changing didn't resolve my issue) and that using pragma autonomous_transaction might work. When I tried my trigger as an autonomous transaction I received following error:


That error was solved by adding a commit in the trigger.


So this is my "INSTEAD OF" trigger for the apex_per_people_vw in the APPS schema
(if you create the trigger on top of the view in your own schema you get an insufficient privilege error, see previous posts how I did the grants)

create or replace trigger apex_per_people_vw_trg
instead of insert or update on apex_per_people_vw
declare
  l_person_id                 number;
  l_assignment_id             number;
  l_per_object_version_number number;
  l_asg_object_version_number number;
  l_assignment_sequence       number;
  l_assignment_number         varchar2(4000);
  l_object_version_number     number;
  l_employee_number           varchar2(4000);
  l_effective_start_date      date;
  l_effective_end_date        date;
  l_full_name                 varchar2(4000);
  l_comment_id                number;
  l_name_combination_warning  boolean;
  l_assign_payroll_warning    boolean;
  l_orig_hire_warning         boolean;  
  -- use of Autonomous Transaction needed for this API
pragma autonomous_transaction;
begin
  if INSERTING 
  then
    apps.apex_api_pkg.create_person ( 
      p_hire_date                 => trunc(:NEW.effective_start_date), 
      p_business_group_id         => :NEW.business_group_id,     
      p_last_name                 => :NEW.last_name,     
      p_sex                       => :NEW.sex,
      p_first_name                => :NEW.first_name,     
      p_date_of_birth             => :NEW.date_of_birth,     
      p_email_address             => :NEW.email_address,     
      p_employee_number           => l_employee_number,     
      p_person_id                 => l_person_id,     
      p_assignment_id             => l_assignment_id,     
      p_per_object_version_number => l_per_object_version_number, 
      p_asg_object_version_number => l_asg_object_version_number, 
      p_per_effective_start_date  => l_effective_start_date, 
      p_per_effective_end_date    => l_effective_end_date, 
      p_full_name                 => l_full_name, 
      p_per_comment_id            => l_comment_id, 
      p_assignment_sequence       => l_assignment_sequence, 
      p_assignment_number         => l_assignment_number, 
      p_name_combination_warning  => l_name_combination_warning, 
      p_assign_payroll_warning    => l_assign_payroll_warning, 
      p_orig_hire_warning         => l_orig_hire_warning 
    );
  elsif UPDATING 
  then
    l_object_version_number := :OLD.object_version_number;
    apps.apex_api_pkg.update_person_email (
      p_effective_date            => trunc(:NEW.effective_start_date),
      p_datetrack_update_mode     => 'CORRECTION',
      p_person_id                 => :OLD.person_id,
      p_email_address             => :NEW.email_address,
      p_object_version_number     => l_object_version_number,
      p_employee_number           => l_employee_number,
      p_effective_start_date      => l_effective_start_date,
      p_effective_end_date        => l_effective_end_date,
      p_full_name                 => l_full_name,
      p_comment_id                => l_comment_id,
      p_name_combination_warning  => l_name_combination_warning,
      p_assign_payroll_warning    => l_assign_payroll_warning,
      p_orig_hire_warning         => l_orig_hire_warning
    );  
  end if;
  -- as we user autonomous transaction a commit or rollback is necessary
  commit;
exception
  when others
  then
    rollback;
end;


On page 10 of the Oracle whitepaper about the integration of E-Business Suite and APEX you find another working example of the updateable view method with the FND_FLEX_VAL_API without an autonomous transaction.

So which technique is best? Using updateable views or call the APIs from the Page Processes?
It depends, they have both advantages and disadvantages, but sometimes you don't have a choice and can't use the trigger method.

Advantages using triggers:
  • Code in one place regardless which APEX page/process inserts/updates/deletes on the view
  • Able to use the APEX wizards to build report and form on top of the view with build-in APEX processes
Disadvantage (or things you should know) using triggers:
  • Not every EBS API seems to work through triggers
  • Might need to use Autonomous transaction in your trigger to get it working, but what are the side effects? (especially as I don't know what is going on inside the EBS API call)
  • Recreating the view will lose the trigger, so make sure you can recreate the trigger
  • APEX automatic row locking might interfere with EBS locking (the Oracle whitepaper doesn't mention anything of that, but Paolo posted a comment about this, also see documentation of FSP_DML_LOCK_ROW - I didn't experience this yet)
  • Other people might not know about those triggers, so make sure it's clear to who you work with or who will take over the project to tell him you use triggers 
  • Read Tom Kyte's article about triggers, so you know when to use/not use them

Advantages using custom process
  • Full control when you want to run some code
  • Works regardless of EBS API
  • Different logic possible on different screens on the same data
  • Easier to debug (how much time a process takes, debug output etc.)
Disadvantages using custom process
  • Takes more time to build the pages and processes manually. There is a wizard to build on top of a procedure too, but I find that still slower than if you can use the wizard to build on top of a view/table.
  • More code if you need to call the same procedure on multiple pages

If you experienced other advantages/disadvantages, feel free to comment.


Previous related posts:

Wednesday, February 08, 2012

APEX SQL Workshop and Wizards issues in EBS environment

E-Business Suite (EBS) is a beast... it's the biggest environment I've ever seen.
If you run this query in the APPS schema:


select object_type, count(*)
  from user_objects
group by object_type
order by 2 desc


This is the result:


Over 50,000 packages, over 40,000 synonyms, over 30,000 views in one schema... impressive!

As I don't have direct access to the Oracle EBS database, I do everything through APEX.
I setup two workspaces, one linked to the APPS schema, where I only use SQL Workshop to access the objects and another workspace which holds my applications and database objects and grants to some objects in the APPS schema.

The issue with the Object Browser in SQL Workshop (in APEX) is that it will only show 10,000 objects.
So there is a chance that if you create your own view, synonym or package that you won't see it.
(There are ways to increase this limit, but as it's unsupported I won't mention how. And you would need to have access to the web server to make the change, which you might not have either.)


Even if you try to filter the result, it won't show up. Obviously you won't see many environments having over 10,000 objects in one schema, but I thought to post it anyway as some other EBS customers might hit the issue too and maybe when the Public Oracle Cloud is online and you do everything through the APEX screens you might hit this issue too (if it was an EBS environment for example).

So how do you get around this? If you want to change your view or you want to see the definitions of that view you can do:

select text
  from user_views 
 where view_name = 'PER_PEOPLE_F'

You can copy the definition, make the change and run it in SQL Workshop > SQL Commands to update your view. Querying the user_objects will show you all the objects for example starting with APEX:


select *
  from user_objects
 where object_name like 'APEX%'


For packages (and package bodies) it's a pain, especially if you want to develop those packages online. If you can't select the package you don't have the nice editor in APEX, so it makes development a bit harder. You could argue you should do development in for example Oracle SQL Developer or others, but in my case I didn't have direct access to the database, only through a browser...


The other issue I encountered was in the APEX wizards when I created a Form on a Procedure, when the package/procedure resides in the APPS schema. It kept spinning and spinning and after a couple of minutes I just closed my browser because I didn't want to wait longer.


I guess it's because the APPS schema is so big and reading from the data dictionary takes a long time, potentially even longer than the web server timeout.

The workaround here is to add a blank page, create an HTML region and add your items manually.
Also don't forget to add your process to the page to fetch and/or store the record.


Hope that helps some people who can't find their objects in SQL Workshop or who find the wizard is slow or doesn't complete in an EBS environment.

Release of 3.1 versions of SQL Developer and Modeler

It looks like today was releasing day for the Oracle SQL Developer team:

You can download Oracle SQL Developer 3.1 (07) or read on the new features e.g. DBA Utilities, Datapump wizard, a new Database Diff, ...


And also Oracle SQL Developer Data Modeler got a new version: 3.1.0 (700) which hold some new features like synchronisation between model and data dictionary and versioning of designs. You can download it here.


Installation and migration of settings of my previous version went without any issues. I couldn't live without those tools anymore.

Monday, February 06, 2012

Creating EBS data from APEX using APIs (2)

Previously we used the HR_PERSON_API to update the email address of a person.
In this post we will use the HR_EMPLOYEE_API to add a person.

The first thing to do is to look at the definition of the create employee call in the Oracle Integration Repository. (Login in EBS, in the navigator go to Integrated SOA Gateway, next go to Human Resource Suites > Human Resources > Employee and select Employee > Create Employee)


The second thing I tend to do is test the API in SQL Workshop in a PL/SQL anonymous block from a workspace linked to the APPS schema.


When I ran that block of code I got a ORA-01403: no data found error. I verified my call multiple times but I couldn't see what I was doing wrong. Finally I send the statement to Thierry Vergult from Popay as I was out of ideas. He told me to try a trunc around sysdate for the parameter p_hire_date.
I verified the API again and p_hire_date should be a date, sysdate in my opinion is the most known date, so I was a bit sceptic at first... but I tried it nevertheless... and it worked!
This blew me out of the water! It showed me again that the API is full of surprises and whenever you hit an issue, ask a more experienced EBS person for ideas :-)

So once that was working I included the create procedure in my apex_api_pkg.


procedure create_person (
    p_hire_date                 in date,
    p_business_group_id         in number,
    p_last_name                 in varchar2,
    p_first_name                in varchar2,
    p_date_of_birth             in date,
    p_sex                       in varchar2,
    p_email_address             in varchar2,
    p_employee_number           in out varchar2,
    p_person_id                 out number,
    p_assignment_id             out number,
    p_per_object_version_number out number,
    p_asg_object_version_number out number,
    p_per_effective_start_date  out date,
    p_per_effective_end_date    out date,
    p_full_name                 out varchar2,
    p_per_comment_id            out number,
    p_assignment_sequence       out number,
    p_assignment_number         out varchar2,
    p_name_combination_warning  out boolean,
    p_assign_payroll_warning    out boolean,
    p_orig_hire_warning         out boolean
)
is
  l_employee_number           varchar2(4000);
  l_person_id                 number;
  l_assignment_id             number;
  l_per_object_version_number number;
  l_asg_object_version_number number;
  l_per_effective_start_date  date;
  l_per_effective_end_date    date;
  l_full_name                 varchar2(4000);
  l_per_comment_id            number;
  l_assignment_sequence       number;
  l_assignment_number         varchar2(4000);
  l_name_combination_warning  boolean;
  l_assign_payroll_warning    boolean;
  l_orig_hire_warning         boolean;
begin
  hr_employee_api.create_employee ( 
    p_hire_date                 => p_hire_date, 
    p_business_group_id         => p_business_group_id,     
    p_last_name                 => p_last_name,     
    p_sex                       => p_sex,
    p_first_name                => p_first_name,     
    p_date_of_birth             => p_date_of_birth,     
    p_email_address             => p_email_address,     
    p_employee_number           => l_employee_number,     
    p_person_id                 => l_person_id,     
    p_assignment_id             => l_assignment_id,     
    p_per_object_version_number => l_per_object_version_number, 
    p_asg_object_version_number => l_asg_object_version_number, 
    p_per_effective_start_date  => l_per_effective_start_date, 
    p_per_effective_end_date    => l_per_effective_end_date, 
    p_full_name                 => l_full_name, 
    p_per_comment_id            => l_per_comment_id, 
    p_assignment_sequence       => l_assignment_sequence, 
    p_assignment_number         => l_assignment_number, 
    p_name_combination_warning  => l_name_combination_warning, 
    p_assign_payroll_warning    => l_assign_payroll_warning, 
    p_orig_hire_warning         => l_orig_hire_warning 
  );
end create_person;


The finally step was to add a form on my page with the items and a process that calls the API.


The page process Create User looks like this:


Running the page shows you the create form.

Note that I didn't make all fields visible, just the fields to get the proof of concept working.

In this post we called the API to create a person from within an APEX page. The catch here was to trunk the p_hire_date parameter.

Previous related posts:

Sunday, February 05, 2012

Updating EBS data from APEX using APIs (1)

I didn't expect to many technical issues using E-Business Suite (EBS) APIs in APEX as it's basically a call to PL/SQL packages.

Next to that the EBS APIs seems to be well documented. I first didn't realise, but for a long time I was using the Oracle Integration Repository for EBS R11 (whereas I'm using R12).



For EBS R12 the Oracle Integration Repository ships as part of the E-Business Suite. To access it, in the Navigator menu, select the Integrated SOA Gateway responsibility, then click on the Integration Repository link.


Using the Oracle Integration Repository I would have found it more useful if I could define the number of results (maybe you can, but I didn't find that setting). At the moment I get only 10 results at a time, which is too low for me.

I also found it not that easy to find the right API to use. I guess it comes by experience. Next to that, the parameters are not always the same, so the API could be more consistent.

For example I want to create and edit a person. In the navigator I went to Human Resources Suite > Human Resource. That was logic for me. Next I looked into the list and saw Employee, so that was a logic choice for me. In there I found the HR_EMPLOYEE_API. That API allows to create an employee. So far so good, but where is the edit? I couldn't really find it, until I asked a friend and he told me to look for person. So when I went to HR Person(1) in the navigator I saw the HR_PERSON_API and in there you find the update and delete of a person.


When you look at the parameters; in HR_EMPLOYEE_API.CREATE_EMPLOYEE you see a parameter p_per_comments, in HR_PERSON_API.UPDATE_PERSON you see a parameter p_comments. It would have been easier if the parameters were consistent.

So once I got familiar with APIs, I started with the integration in my APEX app. Here are the steps to drill-down into a person from the people report (see previous post) and edit his or her email address.

Just as with the views I find it a good practice to not grant execute on the entire libraries of EBS to your own schema. I prefer to create my own packages in the APPS schema e.g. apex_api_pkg and call the correct API calls from there. The advantage is that you can add logging to your own package or do some other extra logic in there. For example APEX passes typically back values as strings, but some API calls need to be passed (next to varchar2) as a date, a number or a boolean. So you could choose to have all input strings in varchar2 in the apex_api_pkg and do the conversion inside the package to the correct one. In the below code I didn't do that, instead I went for an almost 1-on-1 mapping.


create or replace package apex_api_pkg
as


procedure update_person_email (
    p_effective_date            in date,
    p_datetrack_update_mode     in varchar2,
    p_person_id                 in number,
    p_email_address             in varchar2,
    p_object_version_number     in out number,
    p_employee_number           out varchar2,
    p_effective_start_date      out date,
    p_effective_end_date        out date,
    p_full_name                 out varchar2,
    p_comment_id                out number,
    p_name_combination_warning  out boolean,
    p_assign_payroll_warning    out boolean,
    p_orig_hire_warning         out boolean
);


end apex_api_pkg;
/


create or replace package body apex_api_pkg
as


procedure update_person_email (
    p_effective_date            in date,
    p_datetrack_update_mode     in varchar2,
    p_person_id                 in number,
    p_email_address             in varchar2,
    p_object_version_number     in out number,
    p_employee_number           out varchar2,
    p_effective_start_date      out date,
    p_effective_end_date        out date,
    p_full_name                 out varchar2,
    p_comment_id                out number,
    p_name_combination_warning  out boolean,
    p_assign_payroll_warning    out boolean,
    p_orig_hire_warning         out boolean
)
is
  l_object_version_number     number;
  l_employee_number           varchar2(4000);
  l_effective_start_date      date;
  l_effective_end_date        date;
  l_full_name                 varchar2(4000);
  l_comment_id                number;
  l_name_combination_warning  boolean;
  l_assign_payroll_warning    boolean;
  l_orig_hire_warning         boolean;  
begin
  l_object_version_number := p_object_version_number;


  hr_person_api.update_person (
    p_effective_date            => p_effective_date,
    p_datetrack_update_mode     => p_datetrack_update_mode,
    p_person_id                 => p_person_id,
    p_email_address             => p_email_address,
    p_object_version_number     => l_object_version_number,
    p_employee_number           => l_employee_number,
    p_effective_start_date      => l_effective_start_date,
    p_effective_end_date        => l_effective_end_date,
    p_full_name                 => l_full_name,
    p_comment_id                => l_comment_id,
    p_name_combination_warning  => l_name_combination_warning,
    p_assign_payroll_warning    => l_assign_payroll_warning,
    p_orig_hire_warning         => l_orig_hire_warning
  );
end update_person_email;


end apex_api_pkg;
/


The next thing is to grant execute privileges on this package to your own user which is linked to your APEX workspace:


grant execute on apex_api_pkg to apex_ebs;


In the APEX application we create a new Form based on this procedure. I found that the APEX wizard didn't work in my case (but more on that in another post). I created a new Blank Page, add a region to it and a couple of items.


From my report I created an edit link to this new page and I pass the value of PERSON_ID to this page.
As I want to see the original data in my form (note that I didn't include all fields available in the API) I added a Fetch data process of type PL/SQL anonymous block with this code:


select full_name, email_address, effective_start_date, employee_number, object_version_number
  into :P2_FULL_NAME, :P2_EMAIL_ADDRESS, :P2_EFFECTIVE_DATE, :P2_EMPLOYEE_NUMBER, :P2_OBJECT_VERSION_NUMBER
  from apex_per_people_vw
 where person_id = :P2_PERSON_ID;

When you hit the Apply Changes button I want the email address to update, that is why we have the Update email Process in Page Processing. The PL/SQL code is as follows:


declare
  l_object_version_number     number;
  l_employee_number           varchar2(4000);
  l_effective_start_date      date;
  l_effective_end_date        date;
  l_full_name                 varchar2(4000);
  l_comment_id                number;
  l_name_combination_warning  boolean;
  l_assign_payroll_warning    boolean;
  l_orig_hire_warning         boolean;  
begin
  l_object_version_number := to_number(:P2_OBJECT_VERSION_NUMBER);


  apps.apex_api_pkg.update_person_email (
    p_effective_date            => to_date(:P2_EFFECTIVE_DATE, 'DD-MON-YYYY'),
    p_datetrack_update_mode     => :P2_UPDATE_MODE,
    p_person_id                 => to_number(:P2_PERSON_ID),
    p_email_address             => :P2_EMAIL_ADDRESS,
    p_object_version_number     => l_object_version_number,
    p_employee_number           => l_employee_number,
    p_effective_start_date      => l_effective_start_date,
    p_effective_end_date        => l_effective_end_date,
    p_full_name                 => l_full_name,
    p_comment_id                => l_comment_id,
    p_name_combination_warning  => l_name_combination_warning,
    p_assign_payroll_warning    => l_assign_payroll_warning,
    p_orig_hire_warning         => l_orig_hire_warning
  );
end;

I just do the necessary to update the email, I don't do that much yet with the output parameters, but you could transfer that back to the page if you wanted to. Also note that the package is in the APPS schema, so don't forget to add the owner in front of it. You could create a synonym in your own schema if you preferred. For the view I find it important to have those in my own schema too, for packages only if I wanted to add some extra logic to it.

Running the page shows you the below form.

In EBS there are different ways (UPDATE, CORRECTION, UPDATE_OVERRIDE, UPDATE_CHANGE_INSERT), to update the record, as I wanted to test them out, I added the Update Mode select list to the form. An UPDATE you can only do once per day, CORRECTION is what I used to update the same record multiple times.

Note that we still didn't login into our APEX application as an EBS user, so EBS doesn't know who I'm. As EBS is keeping an audit of the records, I (the logged in APEX user) won't be seen in those audit records, instead it will be a general one (EBS sysadmin I suppose). In the next posts we will authenticate with EBS integrate tighter.




In this post the purpose was to make an EBS API call to update an email address of a person. While playing with the EBS API I came across some challenges and I had to ask for advice to more experienced EBS people. Which will also be shown in the next post; where I will create a person with the API...

Previous related posts:

Friday, February 03, 2012

Querying EBS data from APEX using Views

Integrating APEX and EBS by using views is one of the easiest solutions (at first sight!).

This is the first scenario, where I have an APEX application and I want to integrate with data sitting in EBS.

Pre-requisites:
  • APEX is installed in the same database as EBS (see previous post). 
  • My APEX application (actually Workspace) is linked to my own (non-EBS) Oracle schema.
If you want to view data coming from EBS in your APEX application, follow these steps:
  1. Identify where the data is in EBS

    If you are not familiar with the data model of EBS, it can be hard to find the right information. A good starting point would be the APPS schema, because that has access to the complete Oracle E-Business Suite data model. You can compare it with the SYSTEM schema, which has access to the entire database.

    This pictures shows an overview of the APPS schema and base product schemas.



    You can read more about the APPS schema in the EBS documentation.

    In my example I wanted to find the people that are in my organisation (HR). I started to look for views that would give me that information. My first query was like this:

    select object_name
      from user_objects
     where object_name like '%PEOPLE%'
       and object_type = 'VIEW'
    order by 1 


    That query returned 82 rows in my environment. In the results I saw e.g. ADS_PEOPLE_V, HRBG_PEOPLE, PER_ALL_PEOPLE, PER_PEOPLE, PER_PEOPLE_F etc.

    I started to look at the definitions of those, but if you are not familiar with EBS it's hard to know which one is the one you need. So my recommendation would definitely be; when you are not that familiar with EBS, talk with somebody who knows more about it. For me that is the case, I only started to look into EBS and actually do something with it, a few weeks ago.

    When I talked to somebody more experienced in EBS, he told me I probably wanted to look at PER_ALL_PEOPLE_F. Hmm, that wasn't in the result set of the above query. After investigating a bit more PER_ALL_PEOPLE_F is a synonym for HR.PER_ALL_PEOPLE_F.

    I wanted to understand the naming convention in EBS a bit better e.g. for the PER%PEOPLE% objects.



    Below I created a table how I interpret the EBS objects:

    View / Synonym (^) count(*) count(distinct person_id) Interpretation
    per_all_people_f (^) 32295 18518 Synonym to real HR table
    per_all_people 0 0 Needs EBS session (record in FND_SESSIONS) so it knows what you can see
    per_all_people_d 32295 18518 All records but shows translated text if user settings are applied
    per_people 0 0 Needs EBS session, shows effective records based on user's date
    per_people_f 32295 18518 EBS security implemented, you only see records you are allowed to see
    per_people_v 0 0 Needs EBS Session, includes a lot of display text and is language dependend
    per_people_x 18518 18518 EBS security implemented (same as per_people_f), but limits to only the effective records
    (WHERE TRUNC(SYSDATE) BETWEEN EFFECTIVE_START_DATE AND EFFECTIVE_END_DATE)

    So to me PER_PEOPLE_X looks like a good candidate to use in my APEX application. If I'm not logged into the app as an E-Business user I still see all records that are effective at the time I run the query.
  2. Create a view on top of the EBS views and use some naming conventions so it's easy to recognise which objects you created and are not native EBS ones.

    create view apex_per_people_vw as select * from per_people_x
  3. Grant access on that view to the schema that is linked to your APEX workspace and application

    grant select on apex_per_people_vw to apex_ebs
  4. Create a view in your own schema that selects everything from the view in the apps schema.
    We do that so that the views are a one-on-one mapping between schema's, but they show up in the APEX wizards.

    create view apex_per_people_vw as select * from apps.apex_per_people_vw
  5. Create an Interactive Report on top of the view

This first examples shows how you can view data from EBS in your own APEX application. We can now create a calendar, charts etc. in APEX based on the data coming from EBS. In the next post I will show how you can edit this data.