Tuesday, October 6, 2020

 Have you encountered the below error when trying to cancel a Sales Order?

"Error: The system cannot determine the default tax rate for tax CITY and tax status STANDARD. Please contact your tax manager either to specify a default tax rate code for this tax status and date &DATE or to define appropriate rate determination rules. (TAX_DET_DATE=20-DEC-19)"

This is an extremely frustrating feature in EBS as the documentation does not give any reasonable clue  why this is happening and besides, what do we care about the tax rate when the order is to be cancelled anyway?

I tried to find where the TAX_DET_DATE comes from and it seemed like this should be the TAX_DATE from OE_ORDER_LINES_ALL.


Well, it is not.


It is PROMISE_DATE from OE_ORDER_LINES_ALL.


This is beyond silly, but when I pointed this out to DEV they just shrugged and said that works as designed. 

Anyway, change the Promise Date on your order lines to be today any you will be able to cancel the order.

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;
/

Tuesday, December 16, 2014

Correct unbalanced subledger entries

Here  a practical application of the function xx_ccID.switch_CCID_segment  used to correct entries in the Inventory and WiP subledgers where Debit and Credit are posted to accounts with different balancing segments.

This problem generates this error when running the Create Accounting in Cost Management - SLA

Message : The Legal Entity segment value for the account code combination
() specified in line () is not assigned to the ledger ()
Please update the Legal Entity segment value assignments for the ledger in the Accounting
Configuration page or use another Legal Entity segment value.

Description of the fix:

The first cursor establishes list of transactions where the segments are mismatching.

The second cursor loops through each of those transactions and finds the entry where the balancing segment of the associated legal entity does not match the balancing segment of the entry, then corrects it using the xx_ccID.switch_CCID_segment function described in my previous post.

Optionally it moves the transaction to the  current accounting period to make sure the period is not closed.

This is a relatively simple script with 2 important assumptions:


  • The entry with wrong balancing segment has all the other segments correct. This  is probably true more often than not, but doesn't have to be. It depends on the nature of the problem. If, in example, the offsetting account for cost adjustment was typed in wrong, changing just the balancing segment may actually make things worse as the fix will post the entries to a valid but wrong account. 
  • All operating units reside in separate legal entities or have the same balancing segment if they reside in one legal entity. This is true in most implementations but not in all.


The actual code for Inventory subledger:

Declare

v_transaction_id number;
--v_COA number;
v_correct_account number;
i number;
j number;

cursor a is -- find all the material transactions that have balancing segment mismatch between CR and DT
-- this is to speed up cursor b
select distinct a.transaction_id from
mtl_transaction_accounts a, mtl_transaction_accounts b,
gl_code_combinations ac, gl_code_combinations bc
where
a.transaction_id=b.transaction_id
and a.reference_account=ac.CODE_COMBINATION_ID
and b.reference_account=bc.CODE_COMBINATION_ID
and ac.segment1!=bc.segment1;

cursor b is -- find the actual accounting entries for material trx from previous cursor
select mta.transaction_id, mta.inv_sub_ledger_id, gcc.chart_of_accounts_id, fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                gcc.chart_of_accounts_id,--v_COA,
                                xal.CODE_COMBINATION_ID) account, gle.FLEX_SEGMENT_VALUE org_balancing_segment,
                                xal.accounting_class_code,
                                 mta.reference_account, xah.ae_header_id, xal.ae_line_num,
                                 qa_moac_pkg.derive_ou_id(mmt.organization_id),
                                 mta.transaction_date, xah.ACCOUNTING_DATE, xah.period_name, xe.event_id, xe.APPLICATION_ID
                                  from
XLA_DISTRIBUTION_LINKS xdl,
XLA_AE_HEADERS xah,
XLA_AE_LINES xal,
XLA_EVENTS xe,
MTL_TRANSACTION_ACCOUNTS  mta,
mtl_material_transactions mmt,
hr_organization_information hoi,
gl_legal_entities_bsvs  gle,
gl_code_combinations gcc
where
xdl.source_distribution_type = 'MTL_TRANSACTION_ACCOUNTS'
and xdl.source_distribution_id_num_1(+) = mta.inv_sub_ledger_id
and xah.ae_header_id(+) = xdl.ae_header_id
and  xdl.ae_line_num = xal.ae_line_num(+)
and xah.application_id = xal.application_id
and xah.ae_header_id = xal.ae_header_id
and xe.application_id=xah.application_id
and xe.event_id=xah.event_id
and mta.transaction_id=mmt.transaction_id(+)
and hoi.organization_id(+) = mmt.organization_id
and hoi.org_information_context = 'Accounting Information'
and to_number(hoi.ORG_INFORMATION2)=gle.LEGAL_ENTITY_ID(+)
and mta.reference_account=gcc.code_combination_id
and substr(fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                gcc.chart_of_accounts_id,--v_COA,
                                xal.CODE_COMBINATION_ID),1,length(gle.FLEX_SEGMENT_VALUE))!=gle.FLEX_SEGMENT_VALUE --code combination starts with wrong balancing segment
and
 mta.transaction_id =v_transaction_id;




BEGIN

i:=0;



for x in a loop
v_transaction_id:=x.transaction_id;

for y in b loop
i:=i+1;
v_correct_account:=XX_CCID.switch_CCID_segment(y.reference_account,
y.chart_of_accounts_id,
1,
y.org_balancing_segment,'.'); -- get the CCID with correct balancing segment

dbms_output.put_line('---------------------------');
dbms_output.put_line('transaction_Id= '||y.transaction_id);
dbms_output.put_line('balancing segment ='||y.org_balancing_segment);
dbms_output.put_line('original account = '||fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                y.chart_of_accounts_id,--v_COA,
                                y.reference_account));
dbms_output.put_line('corrected account = '||fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                y.chart_of_accounts_id,--v_COA,
                                v_correct_account));
dbms_output.put_line('corrected account = '||fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                y.chart_of_accounts_id,--v_COA,
                                v_correct_account));
dbms_output.put_line('original period = '||y.period_name);


--update the base accounting table
update mtl_transaction_accounts
set reference_account=v_correct_account
where inv_sub_ledger_id=y.inv_sub_ledger_id;

-- update the subledger acct tabels to keep them in sync
update XLA_AE_LINES set CODE_COMBINATION_ID=v_correct_account
where ae_header_id=y.ae_header_id
and AE_LINE_NUM=y.AE_LINE_NUM;

-- optional date update so that the transaction moves to the current accounting period
update xla_events set
event_date =sysdate,
 REFERENCE_DATE_1 =sysdate,
last_updated_by=2021,
last_update_date=sysdate
where event_id=y.event_id
and application_id=y.application_id;



end loop;
end loop;

dbms_output.put_line('  ');
dbms_output.put_line('  ');
dbms_output.put_line('============================== ');
dbms_output.put_line('total number of corrected entries = '||i);

rollback; -- change this to Commit; during final execution

END;

and for the WIP subledger

Declare

v_transaction_id number;
v_COA number;
v_correct_account number;
i number;
j number;

cursor a is -- find all the wip transactions that have balancing segment mismatch between CR and DT
-- this is to speed up cursor b
select distinct a.transaction_id from
WIP_transaction_accounts a, WIP_transaction_accounts b,
gl_code_combinations ac, gl_code_combinations bc
where
a.transaction_id=b.transaction_id
and a.reference_account=ac.CODE_COMBINATION_ID
and b.reference_account=bc.CODE_COMBINATION_ID
and ac.segment1!=bc.segment1;

cursor b is -- find the actual accounting entries for material trx from previous cursor
select mta.transaction_id, mta.wip_sub_ledger_id, gcc.chart_of_accounts_id, fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                gcc.chart_of_accounts_id,--v_COA,
                                xal.CODE_COMBINATION_ID) account, gle.FLEX_SEGMENT_VALUE org_balancing_segment,
                                xal.accounting_class_code,
                                 mta.reference_account, xah.ae_header_id, xal.ae_line_num,
                                 qa_moac_pkg.derive_ou_id(mmt.organization_id),
                                 mta.transaction_date, xah.ACCOUNTING_DATE, xah.period_name, xe.event_id, xe.APPLICATION_ID
                                  from
XLA_DISTRIBUTION_LINKS xdl,
XLA_AE_HEADERS xah,
XLA_AE_LINES xal,
XLA_EVENTS xe,
WIP_TRANSACTION_ACCOUNTS  mta,
wip_transactions mmt,
hr_organization_information hoi,
gl_legal_entities_bsvs  gle,
gl_code_combinations gcc
where
xdl.source_distribution_type = 'WIP_TRANSACTION_ACCOUNTS'
and xdl.source_distribution_id_num_1(+) = mta.wip_sub_ledger_id
and xah.ae_header_id(+) = xdl.ae_header_id
and  xdl.ae_line_num = xal.ae_line_num(+)
and xah.application_id = xal.application_id
and xah.ae_header_id = xal.ae_header_id
and xe.application_id=xah.application_id
and xe.event_id=xah.event_id
and mta.transaction_id=mmt.transaction_id(+)
and hoi.organization_id(+) = mmt.organization_id
and hoi.org_information_context = 'Accounting Information'
and to_number(hoi.ORG_INFORMATION2)=gle.LEGAL_ENTITY_ID(+)
and mta.reference_account=gcc.code_combination_id
and substr(fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                gcc.chart_of_accounts_id,--v_COA,
                                xal.CODE_COMBINATION_ID),1,length(gle.FLEX_SEGMENT_VALUE))!=gle.FLEX_SEGMENT_VALUE --code combination starts with wrong balancing segment
and
 mta.transaction_id =v_transaction_id;




BEGIN

i:=0;



for x in a loop
v_transaction_id:=x.transaction_id;

for y in b loop
i:=i+1;
v_correct_account:=XX_CCID.switch_CCID_segment(y.reference_account,
y.chart_of_accounts_id,
1,
y.org_balancing_segment,'.'); -- get the CCID with correct balancing segment

dbms_output.put_line('---------------------------');
dbms_output.put_line('transaction_Id= '||y.transaction_id);
dbms_output.put_line('balancing segment ='||y.org_balancing_segment);
dbms_output.put_line('original account = '||fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                y.chart_of_accounts_id,--v_COA,
                                y.reference_account));
dbms_output.put_line('corrected account = '||fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                y.chart_of_accounts_id,--v_COA,
                                v_correct_account));
dbms_output.put_line('corrected account = '||fnd_flex_ext.get_segs ('SQLGL',
                                'GL#',
                                y.chart_of_accounts_id,--v_COA,
                                v_correct_account));
dbms_output.put_line('original period = '||y.period_name);


--update the base accounting table
update WIP_transaction_accounts
set reference_account=v_correct_account
where WIP_SUB_LEDGER_ID=y.WIP_SUB_LEDGER_ID;

-- update the subledger acct tabels to keep them in sync
update XLA_AE_LINES set CODE_COMBINATION_ID=v_correct_account
where ae_header_id=y.ae_header_id
and AE_LINE_NUM=y.AE_LINE_NUM;

-- optional date update so that the transaction moves to the current accounting period
update xla_events set
event_date =sysdate,
 REFERENCE_DATE_1 =sysdate,
last_updated_by=2021,
last_update_date=sysdate
where event_id=y.event_id
and application_id=y.application_id;



end loop;
end loop;

dbms_output.put_line('  ');
dbms_output.put_line('  ');
dbms_output.put_line('============================== ');
dbms_output.put_line('total number of corrected entries = '||i);

rollback; -- change this to Commit; during final execution

END;

Switch single segment in account combination (CCID)

Occasionally I have a problem where the Debit and Credit entries in a subledger can’t balance due to mismatched balancing segment. The easy fix would be to change the segment the Code Combination ID (CCID), not the segments themselves, is stored in all the subledger accounting tables. One can’t just simply update the segment in a table but needs to find the proper CCID.

But what if the CCID does not exist, despite the account combination being valid? Simply inserting it into GL_CODE_COMBINATIONS could violate the cross-validation rules.

To address this problem, I wrote the simple function below that takes the original CCID as an input, switches the segment number provided in the p_segment_number parameter to value in the p_switch_to_parameter. 

Since it is all done using standard APIs it either :
  •          returns the existing  valid CCID of the new combination
  •           generates valid CCID if the combination does not exist
  •          returns 0 when the combination is invalid and  no CCID exists or can be generated


This function can be used for data fixes but also for custom accounting programs and table triggers, where one needs to change one or more segments in an account. In example if you want to have the same combination in the Receivables account as in COGS with only the natural account different the AR Autoaccounting rules do not have functionality to accomplish it. You could explore putting a trigger on RA_INTERFACE_LINES_ALL to insert a value into RA_INTERFACE_DISTRIBUTIONS_ALL as long as the function returns non zero CCID

Here is how I designed the function:

1. Translate the CCID into the segment combination

2. Find the number of segments in the combination

3. Replace the segment

5. Translate the new segment combination into a CCID. 


The last part was a bit  tricky because the CCID for the new combination may or may not exist and I didn't want to get into building a logic that would check it. In addition to that there was always risk that an invalid combination can be created, so it had to be validated against GL cross validation rules. Under other circumstances I might have used account generator workflow but I wanted to keep it as simple as possible and to be used in SQL so Workflow was out of  question


Let's translate the above points into APIs used:

1. FND_FLEX_EXT.get_segs('SQLGL', 'GL#',v_coa_id,p_CCID) -- concatenated account

2. FND_FLEX_EXT.breakup_segments(concatenated_segs => v_input_combination
,delimiter => p_delimiter
,segments => v_input_array);

3. v_output_array:=v_input_array;
v_output_array(p_segment_number):=p_switch_to;  -- replace the segment

4. FND_FLEX_EXT.get_ccid('SQLGL','GL#', v_coa_id, fnd_date.date_to_canonical(sysdate), v_output_combination); --recreate the new array as CCID.

Here is an example of how the function can be called:

select ccid,
fnd_flex_ext.get_segs('SQLGL', 'GL#',CHART_OF_ACCOUNTS_ID,ccID) old_code_combination,
new_ccid,
fnd_flex_ext.get_segs('SQLGL', 'GL#',CHART_OF_ACCOUNTS_ID,new_ccID) new_code_combination
from
(
SELECT CHART_OF_ACCOUNTS_ID,a.code_combination_id ccID,
xx_ccID.switch_CCID_segment (CODE_COMBINATION_ID,   --p_CCID
CHART_OF_ACCOUNTS_ID,  --p_coa_id
1,                     --p_segment_number
'02',                  --p_switch_to
'.'                    --p_delimiter
                              )
          new_ccid
  FROM gl_code_combinations a
 WHERE code_combination_id = 21704)







and the actual package code:


CREATE OR REPLACE PACKAGE APPS.XX_CCID
AS
Function switch_CCID_segment (p_CCID IN NUMBER,
p_coa_id IN number,
p_segment_number IN number,
p_switch_to IN varchar2,
p_delimiter IN varchar2)
 return number;


END XX_CCID ;
/

CREATE OR REPLACE package body APPS.XX_CCID
 as

  Function switch_CCID_segment (p_CCID IN NUMBER,
p_coa_id IN number,
p_segment_number IN number,
p_switch_to IN varchar2,
p_delimiter IN varchar2)
 return number IS



v_new_account number;

v_coa_id number;

v_input_combination varchar2(200);
v_output_combination varchar2(200);



v_input_array FND_FLEX_EXT.SegmentArray;

v_output_array FND_FLEX_EXT.SegmentArray;

v_number_of_segments number;



V_ERROR VARCHAR2(2000);

Begin


-- chart of accounts
v_coa_id:=p_coa_id;


-- get the combination from the ccid

v_input_combination := fnd_flex_ext.get_segs('SQLGL', 'GL#',v_coa_id,p_CCID) ;



-- this returns the number of segments but also feeds the segments to the v_input_array

v_number_of_segments:=FND_FLEX_EXT.breakup_segments(concatenated_segs => v_input_combination

,delimiter => p_delimiter

,segments => v_input_array);


v_output_array:=v_input_array;


--switch the segmentvalue
v_output_array(p_segment_number):=p_switch_to;

-- concatenate the new combination into string
v_output_combination:=FND_FLEX_EXT.concatenate_segments(v_number_of_segments,v_output_array,p_delimiter);

-- convert the string to CCID
v_new_account:=FND_FLEX_EXT.get_ccid('SQLGL','GL#', v_coa_id,fnd_date.date_to_canonical(sysdate),v_output_combination);

return v_new_account;


EXCEPTION

When others then

-- in case of failure return the original account

v_new_account:=p_CCID;

return v_new_account;

END switch_CCID_segment;

END XX_CCID;
/


Very simple function that can save tons of time.