Sei sulla pagina 1di 14

Efe Avsar

ORACLE

CREATING CUSTOM ORACLE WORKFLOW USING AME


JANUARY 27, 2015 | EFE AVAR | LEAVE A COMMENT

Oracle Approvals Management (AME) had rst published over ten years ago, and also big question is Why isnt it so popular. I think this is
all about supporting unnecessary extra extensibility which is not required for traditional approval processes. AME includes many
advantages for more exibility/extensibility/functionality on approval processes. If we would like to check some of these advantages; you
can create custom transaction types ,approval groups , approval types. These are de nitions called rules, attributes, conditions, approval
groups, action types.
Most featured functionality that AME provides us is action types like paralel or serial voting. You can use approval types like consensus
(all approvers have to approve to end approval process) , rst responder wins ( rst approver nishes all process). And you can de ne these
approvers dynamically , It supports;
HR Organization Hierarchy
Roles
Users
Most important one: your custom approval processes with your custom rules
For further information about AME you can check these document for AME implementation : Metalink Note 336901.1 andNote
282529.1
In this case, We created a simple custom WF and PL/SQL package for AME rules and noti cation process. Custom WF contains two
processes a main and a subprocess;

In main Work ow process we call AME process with only a return type approved or rejected. So! We are focussing only AME sub-process
in this practice . Now check two notifcations and what function look like ;

These three functions use following package ;


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

CREATE OR REPLACE package apps.xx_ame_approval_pkg is


procedure getAmeTransactionDetail (x_transaction_type in out varchar2 , x_application_id out number );
procedure getNextApprover(itemtypein varchar2,
itemkey in varchar2,
actid in number,
funcmodein varchar2,
resultout out NOCOPY varchar2) ;

procedure updateAmeWithResponse(itemtypein varchar2,


itemkey in varchar2,
actid in number,
funcmodein varchar2,
resultout out nocopy varchar2) ;

END;
/

1
2
3
4

CREATE OR REPLACE package body apps.xx_ame_approval_pkg is

procedure getAmeTransactionDetail (x_transaction_type in out varchar2 , x_application_id out number )


is

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

cursor c1 is
select fnd_application_id as application_id,
transaction_type_id as transaction_type
from ame_transaction_types_v
where transaction_type_id = x_transaction_type;

begin

for crec in c1 loop

x_transaction_type:=crec.transaction_type;
x_application_id := crec.application_id ;

end loop ;

end ;

procedure getNextApprover(itemtypein varchar2,


itemkey in varchar2,
actid in number,
funcmodein varchar2,
resultout out NOCOPY varchar2) IS

E_FAILURE EXCEPTION;
l_transaction_idnumber;
l_next_approver ame_util.approverRecord2;
l_next_approversame_util.approversTable2;
l_next_approvers_countnumber;
l_approver_indexnumber;
l_is_approval_completeVARCHAR2(1);
l_transaction_typeVARCHAR2(200);
l_application_idnumber;
l_role_usersWF_DIRECTORY.UserTable;
l_role_nameVARCHAR2(320) ;
l_role_display_nameVARCHAR2(360);

l_all_approversame_util.approversTable;

cursor c1(p_user_name varchar2) is


select papf.full_name fromfnd_user fu ,
per_all_people_f papf
wherefu.employee_id = papf.person_id
andfu.user_name = p_user_name
and sysdate between papf.EFFECTIVE_START_DATE and nvl(papf.EFFECTIVE_end_DATE,sysdate+1)
and sysdate between fu.start_date and nvl(fu.end_date,sysdate+1) ;

begin

if (funcmode = 'RUN') THEN

-- l_transaction_id :=TO_NUMBER(itemkey);

l_transaction_id:= wf_engine.getItemAttrNumber( itemtype =>itemtype,

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112

itemkey=> itemkey,
aname=> 'AME_TRANSACTION_ID' );

l_transaction_type :=wf_engine.getItemAttrText( itemtype =>itemtype,


itemkey=> itemkey,
aname=> 'AME_TRANSACTION_TYPE' );

getAmeTransactionDetail( l_transaction_type,l_application_id );

ame_api2.getNextApprovers4(applicationIdIn=>l_application_id,
transactionTypeIn=>l_transaction_type,
transactionIdIn=>l_transaction_id,
flagApproversAsNotifiedIn => ame_util.booleanTrue,
approvalProcessCompleteYNOut => l_is_approval_complete,
nextApproversOut=>l_next_approvers);

l_next_approvers_count:=l_next_approvers.count ;

if (l_is_approval_complete = ame_util.booleanTrue) then


resultout:='COMPLETE:'||'APPROVAL_COMPLETE';
return;

--Incase of consensus voting method, next approver count might be zero but there will be pending approvers
elsif (l_next_approvers.Count = 0) then

ame_api2.getPendingApprovers(applicationIdIn=>l_application_id,
transactionTypeIn=>l_transaction_type,
transactionIdIn=>l_transaction_id,
approvalProcessCompleteYNOut => l_is_approval_complete,
approversOut =>l_next_approvers);
end if;

l_next_approvers_count := l_next_approvers.Count;

if (l_next_approvers_count = 0)then
resultout:='COMPLETE:'||'NO_NEXT_APPROVER';
return;
end if;

if (l_next_approvers_count > 0)then


resultout:='COMPLETE:'||'VALID_APPROVER';
--return;
end if;

if (l_next_approvers_count = 1)then

l_next_approver:=l_next_approvers(l_next_approvers.first());
wf_engine.SetItemAttrText( itemtype => itemType,
itemkey=> itemkey,
aname=> 'APPROVER_USER_NAME' ,
avalue => l_next_approver.name);

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166

wf_engine.SetItemAttrText( itemtype => itemType,


itemkey=> itemkey,
aname=> 'APPROVER_DISPLAY_NAME' ,
avalue => l_next_approver.display_name);

/*role name is user name here */


for crec in c1(l_next_approver.name) loop

l_role_display_name:=crec.full_name ;

end loop;
--l_role_name:=

resultout:='COMPLETE:'||'VALID_APPROVER';
--return;

end if;

l_approver_index := l_next_approvers.first();

while ( l_approver_index is not null ) loop

l_role_users(l_approver_index):= l_next_approvers(l_approver_index).name ;

l_approver_index := l_next_approvers.next(l_approver_index);

end loop;

wf_directory.CreateAdHocRole2( role_name => l_role_name


,role_display_name => l_role_display_name
,language => null
,territory => null
,role_description => 'AME ROLE DESC'
,notification_preference => null
,role_users => l_role_users
,email_address => null
,fax => null
,status => 'ACTIVE'
,expiration_date => null
,parent_orig_system => null
,parent_orig_system_id => null
,owner_tag => null
);

wf_engine.setitemattrtext(itemtype => itemtype,


itemkey => itemkey,
aname => 'RECIPIENT_ROLE',
avalue => l_role_name
);
return;

end if; -- run

167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220

exception
when others then
-- The line below records this function call in the error
-- system in the case of an exception.
wf_core.context('xx_ame_approval_pkg',
'getNextApprover',
itemtype,
itemkey,
to_char(actid),
funcmode);
raise;
end getNextApprover;

procedure updateAmeWithResponse(itemtypein varchar2,


itemkey in varchar2,
actid in number,
funcmodein varchar2,
resultout out nocopy varchar2) is
e_failure exception;
l_transaction_idnumber;
l_nid number;
l_gid number;
l_approver_name varchar2(240);
l_resultvarchar2(100);
l_ame_statusvarchar2(20);
l_original_approver_name varchar2(240);
l_forwardeeiname_util.approverrecord2;

l_transaction_type varchar2 (200 byte);


l_application_id number ;

begin

l_transaction_type :=wf_engine.getItemAttrText( itemtype =>itemtype,


itemkey=> itemkey,
aname=> 'AME_TRANSACTION_TYPE');

getAmeTransactionDetail (l_transaction_type,
l_application_id);

l_transaction_id:= wf_engine.getItemAttrNumber( itemtype =>itemtype,


itemkey=> itemkey,
aname=> 'AME_TRANSACTION_ID');

if (funcmode = 'RUN') then

-- l_transaction_id :=itemkey;
l_gid := wf_engine.context_nid;

select responder,notification_id
intol_approver_name,l_nid
from wf_notifications

221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274

---where group_id=l_gid
where group_id=l_gid
and status = 'CLOSED';

l_result := wf_notification.getattrtext(l_nid, 'RESULT');

if (l_result = 'APPROVED') then -- this may vary based on lookup type used for approval
l_ame_status := ame_util.approvedstatus;
elsif (l_result = 'REJECTED') then
l_ame_status := ame_util.rejectstatus;
else -- reject for lack of information, conservative approach
l_ame_status := ame_util.rejectstatus;
end if;
--set approver as approved or rejected based on approver response
ame_api2.updateapprovalstatus2(applicationidin=>l_application_id,
transactiontypein=>l_transaction_type,
transactionidin=>l_transaction_id,
approvalstatusin => l_ame_status,
approvernamein => l_approver_name);

elsif( funcmode = 'TIMEOUT' ) then

l_gid := wf_engine.context_nid;

select responder,notification_id
into l_approver_name,l_nid
from wf_notifications
where group_id=l_gid;
--and status = 'CLOSED';

l_result := wf_notification.getattrtext(l_nid, 'RESULT');

if (l_result = 'APPROVED') then -- this may vary based on lookup type used for approval
l_ame_status := ame_util.approvedstatus;
elsif (l_result = 'REJECTED') then
l_ame_status := ame_util.rejectstatus;
else -- reject for lack of information, conservative approach
l_ame_status := ame_util.rejectstatus;
end if;
--set approver as approved or rejected based on approver response
ame_api2.updateapprovalstatus2(applicationidin=>l_application_id,
transactiontypein=>l_transaction_type,
transactionidin=>l_transaction_id,
approvalstatusin => l_ame_status,
approvernamein => l_approver_name);

elsif( funcmode = 'TRANSFER' ) then

--l_transaction_id :=itemkey;
l_forwardeein.name :=wf_engine.context_new_role;
l_original_approver_name:= wf_engine.context_original_recipient;

275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302

ame_api2.updateapprovalstatus2(applicationidin=>l_application_id ,
transactiontypein=>l_transaction_type,
transactionidin=>l_transaction_id,
approvalstatusin => 'FORWARD',
approvernamein => l_original_approver_name,
forwardeein => l_forwardeein );

end if; -- run

resultout:= wf_engine.eng_completed || ':' || l_result;

exception
when others then
wf_core.context('xx_ame_approval_pkg',
'updateAmeWithResponse',
itemtype,
itemkey,
to_char(actid),
funcmode);

raise;
end updateAmeWithResponse;

END;
/

What we had done in package above is simple ;


in getNextApprover : we used AME engine to nd existing situation of our noti cation and approvers. Then we created a role for the
current approver then set it as noti cation performer .
in updateAmeWithResponse : we updated current noti cation status using AME engine(ame_api2 package) and control if it is in timeout
period or not , also if it is nal approver of AME approver group.
Now we begin to start this WF process from this following package below for Orders which have holds (startApprovalWF procedure) . We
can log this process (insertLog procedure) . We set document type attribute to set a custom OAF page for noti cation content. We also
have custom business rules like timeout for noti cation process just in case our approver doesnt give a response to it . (getTimeoutDay
function). In this case our AME transaction type has the same name as Work ow item type . Package ;
1
2
3
4

CREATE OR REPLACE package xx_ame_start_appr_pkg is

procedure startApprovalWF(errbuff out varchar2,


retcode out number,

5
6
7
8
9
10
11
12
13
14
15
16

p_order_number in number,
p_hold_type in varchar2 ,
p_comment in varchar2
) ;
function getTimeoutDay (p_header_id in number) return number ;
procedure insertLog (itemtype IN VARCHAR2,
itemkey IN VARCHAR2,
p_log in varchar2
) ;

end ;
/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

CREATE OR REPLACE package apps.xx_ame_start_appr_pkg is

/*This procedure starts our Approval WF via setting AME transaction types and
required information (this can be also called as Concurrent Program)*/
procedure startApprovalWF(errbuff out varchar2,
retcode out number,
p_order_number in number,
p_hold_type in varchar2,
p_comment in varchar2
) is
cursor c1 is
select oh.header_id ,
oh.order_number
from oe_order_holds_all ha ,
oe_order_headers_all oh
where ha.header_id = oh.header_id
and oh.ORDER_NUMBER= p_order_number
and ha.released_flag = 'N';

itemkey varchar2(240);
itemtype varchar2(8);
created_by_user_name varchar2(250);
l_seq number ;
is_entered boolean := false ;

begin

select xxxl.xxxl_wf_item_key.nextval into l_seq from dual ;


itemkey := l_seq||'-'||p_hold_type;
itemtype :='YOUR CUSTOM WF NAME';

for crec in c1 loop


insertLog (itemtype ,
itemkey ,
'startApprovalWF procedure has been called');
wf_engine.createprocess (itemtype,

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

itemkey ,
'YOUR MAIN PROCESS NAME');

select user_name into created_by_user_name


from fnd_user
where user_id = fnd_profile.value('USER_ID');
wf_engine.setItemAttrText(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'CREATED_BY_USER_NAME',
avalue=>created_by_user_name) ;
wf_engine.setItemAttrText(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'HOLD_TYPE',
avalue=>p_hold_type) ;
wf_engine.setItemAttrText(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'AME_TRANSACTION_TYPE',
avalue=>'YOUR CUSTOM WF NAME') ;
wf_engine.setItemAttrNumber(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'AME_TRANSACTION_ID',
avalue=>crec.header_id) ;
wf_engine.setItemAttrNumber(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'ORDER_HEADER_ID',
avalue=>crec.header_id
) ;
wf_engine.setItemAttrNumber(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'ORDER_NUMBER',
avalue=>crec.order_number
) ;
wf_engine.setItemAttrText(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'START_COMMENT',
avalue=>p_comment) ;
wf_engine.setItemAttrText(itemtype =>itemtype,
itemkey =>itemkey,
aname =>'NUMBER_OF_WAIT_DAYS',
avalue=>getTimeoutDay(crec.header_id )) ;
wf_engine.setitemattrtext (itemtype,
itemkey,
'NTF_HOLD_CONTENT_DOC',
'JSP:/OA_HTML/OA.jsp?page=/xxxl/oracle/apps/xxxl/ame/webui/XxxlAmeNtfRN&xx_order_header_id='||crec.header_id
||'&xx_ntf_type='||p_hold_type

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

||'&xx_item_type='||itemtype
||'&xx_item_key='||itemkey);

wf_engine.startprocess (itemtype,
itemkey);

is_entered := true ;

exit;
end loop;

if (not is_entered) then

insertLog (itemtype ,
itemkey ,
'startApprovalWF no record has been found' );
fnd_file.put_line (fnd_file.LOG,'startApprovalWF no record has been found');
retcode := 2; --error
RETURN;

end if ;
end ;

/*This function defines our custom rules for timeout function in our AME WF*/
function getTimeoutDay (p_header_id in number) return number is
l_control number;
begin

begin

select 1 into l_control from oe_transaction_types_tl ott,


oe_order_headers_all ooh
where language='US'
and ott.name like 'Your Custom Transaction Type'
and ooh.header_id=p_header_id
and ott.transaction_type_id= ooh.order_type_id;

/*Your Custom Transaction Type Order if they wouldn't order until next day at 5:00pm approval process cancel*/
returnround( ((24-to_char(sysdate,'HH24') ))+ 17 )*60;--dakika
--round( ((24-to_char(sysdate,'HH24') )/24)+ 17/24 , 2);

exception when others then


/*If it is not approved in 5 days it will be cancelled */
return 5*24*60;--dakika

end ;

end ;
procedure insertLog (itemtype IN VARCHAR2,
itemkey IN VARCHAR2,

147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164

p_log in varchar2
) is

PRAGMA AUTONOMOUS_TRANSACTION;
l_rec xxxl.xxxl_wf_log%rowtype ;
begin

l_rec.item_type:=itemtype;
l_rec.item_key:=itemkey ;
l_rec.xlog:=p_log;
l_rec.object_name:='xx_ame_start_appr_pkg' ;
l_rec.trnx_Date :=sysdate ;
insert into xxxl.xxxl_wf_log VALUES l_rec ;
commit;

end ;

END ;

I hope this will help to increase in demand for this productive Oracle Engine . We nished Creating Custom Oracle Work ow using AME
process ! Thanks.

Please like & share:

AME

E-BUSINESS SUITE

WORKFLOW

OAF

ORACLE APPLICATION FRAMEWORK

ORACLE APPROVALS MANAGEMENT

ORDER MANAGEMENT

PL/SQL

R12

Potrebbero piacerti anche