Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Development Standards

Download as pdf or txt
Download as pdf or txt
You are on page 1of 134

Development Standards

OR Development Standards

© 2006 Enabler Page 1


Development Standards

1 Table of Contents
1. Table of Contents.......................................................................................................................... 2
2. Introduction .................................................................................................................................. 7
1 DDL Statements ............................................................................................................................ 8
2 DML Statements ........................................................................................................................... 9
2.1 Select....................................................................................................................................... 9
2.2 Insert ....................................................................................................................................... 9
2.3 Update .................................................................................................................................. 10
2.4 Delete .................................................................................................................................... 10
3 SQL*Loader ................................................................................................................................. 11
4 PL/SQL programs ........................................................................................................................ 12
4.1 General Standards ................................................................................................................ 12
4.1.1 Indentation..................................................................................................................... 12
4.1.2 Naming Conventions ...................................................................................................... 12
4.1.3 Fully Document the Specification .................................................................................. 12
4.1.4 Close All Cursors ............................................................................................................. 13
4.1.5 Calling SQL Packages Functions from a Form ................................................................ 13
4.1.6 No Commits, Posts or Savepoints in Functions ............................................................. 13
4.1.7 Calling a Function from Another Function ..................................................................... 13
4.1.8 No Reference to :block.item in Server Functions .......................................................... 14
4.1.9 Package Size ................................................................................................................... 14
4.1.10 User Separators .......................................................................................................... 14
4.1.11 Clarify your ENDs ........................................................................................................ 14
4.2 PL/SQL Objects...................................................................................................................... 14
4.2.1 Function ......................................................................................................................... 14
4.2.2 Procedures ..................................................................................................................... 15
4.2.3 Packages ......................................................................................................................... 15
4.2.4 Triggers ........................................................................................................................... 16
4.2.5 Types .............................................................................................................................. 16
4.3 Package Variables.................................................................................................................. 17
4.4 Defining Input, Output and In/Out Variables ....................................................................... 18
4.5 Exception and Error Handling ............................................................................................... 18
4.6 Conditional and Sequential Control...................................................................................... 20
4.7 Loops ..................................................................................................................................... 20
4.8 Cursor Standards and Use..................................................................................................... 21
4.9 Record Locking ...................................................................................................................... 23
4.10 Performing Tuning ............................................................................................................. 24
4.10.1 General Standards ...................................................................................................... 24
© 2006 Enabler Page 2
Development Standards
4.10.2 UNION vs UNION ALL .................................................................................................. 25
4.10.3 IN vs EXISTS ................................................................................................................. 25
4.10.4 NOT EXISTS vs NOT IN ................................................................................................. 25
5 Oracle Forms ............................................................................................................................... 27
5.1 General Standards ................................................................................................................ 27
5.2 Alerts & emessage ................................................................................................................ 28
5.3 Block Types and their Triggers .............................................................................................. 29
5.4 Form Level Triggers ............................................................................................................... 29
5.5 Single Record Blocks ............................................................................................................. 30
5.5.1 Block Level Triggers ........................................................................................................ 30
5.5.2 Item Level Triggers ......................................................................................................... 31
5.6 Multi Record Blocks .............................................................................................................. 31
5.6.1 Block Level Triggers ........................................................................................................ 32
5.6.2 Item Level Triggers ......................................................................................................... 33
5.6.3 Buttons in Multi-Record Blocks...................................................................................... 33
5.6.4 Date Push Buttons.......................................................................................................... 33
5.6.5 Multi-Language Considerations for Multi-Record Blocks .............................................. 34
5.7 Apply Blocks .......................................................................................................................... 35
5.8 Action Blocks ......................................................................................................................... 35
5.8.1 OK ................................................................................................................................... 36
5.8.2 OK + Repeat.................................................................................................................... 36
5.8.3 Delete ............................................................................................................................. 36
5.8.4 Cancel ............................................................................................................................. 36
5.8.5 LOV Buttons and Calendar Buttons ............................................................................... 36
5.9 Find Blocks ............................................................................................................................ 37
5.10 Boilerplate Text .................................................................................................................. 38
5.11 Busy Cursors ...................................................................................................................... 39
5.12 Calling a Function from a Form ......................................................................................... 39
5.13 Canvas Properties .............................................................................................................. 42
5.14 Capitalization Standards .................................................................................................... 42
5.15 Closing a Window .............................................................................................................. 43
5.16 Currency ............................................................................................................................ 44
5.16.1 About this Topic .......................................................................................................... 44
5.16.2 Technical Implementation .......................................................................................... 46
5.17 Delete Button Standard Code ............................................................................................ 48
5.18 Dynamic Hierarchy ............................................................................................................ 49
5.19 Editors ................................................................................................................................ 50
5.19.1 OG_editor Installation Instructions ............................................................................ 50
5.19.2 Multi-Record Blocks .................................................................................................... 53

© 2006 Enabler Page 3


Development Standards
5.20 Form Level Properties ....................................................................................................... 54
5.21 Form_Success .................................................................................................................... 54
5.22 FORM_TRIGGER_FAILURE ................................................................................................. 57
5.23 GUI Standards .................................................................................................................... 59
5.23.1 Boilerplate Text........................................................................................................... 59
5.23.2 Canvases ..................................................................................................................... 59
5.23.3 Visual Attributes ......................................................................................................... 59
5.23.4 Windows ..................................................................................................................... 60
5.23.5 Blocks .......................................................................................................................... 60
5.23.6 Input/Output .............................................................................................................. 61
5.23.7 Buttons........................................................................................................................ 63
5.24 List of Values (LOV) Buttons .............................................................................................. 65
5.25 Menus ................................................................................................................................ 66
5.25.1 General Menu Standards ............................................................................................ 66
5.25.2 Access Keys for Menu Items ....................................................................................... 66
5.26 Modularized Code ............................................................................................................. 67
5.27 Mouse Navigate ................................................................................................................. 67
5.28 Multiple Language Support ............................................................................................... 68
5.28.1 List Items ..................................................................................................................... 69
5.28.2 Code Description and Dynamically Populated Labels ................................................ 69
5.28.3 Record Groups ............................................................................................................ 69
5.28.4 Labels .......................................................................................................................... 70
5.29 Naming Standards ............................................................................................................. 71
5.29.1 General Naming Standards ......................................................................................... 71
5.29.2 Other Naming Standards ............................................................................................ 72
5.30 Navigation .......................................................................................................................... 72
5.31 Opening Another Form...................................................................................................... 73
5.32 POSTing Form .................................................................................................................... 74
5.33 P_POPULATE_LIST.............................................................................................................. 75
5.34 Reusability & Maintenance ............................................................................................... 76
5.35 RWIDGET ........................................................................................................................... 76
5.35.1 Usage Hints ................................................................................................................. 77
5.35.2 Note on RWIDGET.DISPLAY_ON ................................................................................. 77
5.36 Subclassing in Forms ......................................................................................................... 80
5.37 System Date ....................................................................................................................... 81
5.38 Synchronize........................................................................................................................ 81
5.39 :system Variables ............................................................................................................... 82
5.39.1 :system.record_status ................................................................................................ 82
5.39.2 :system.form_status ................................................................................................... 83

© 2006 Enabler Page 4


Development Standards
5.40 Trigger Scope ..................................................................................................................... 83
5.41 Trigger Usage at Oracle Retail ........................................................................................... 85
5.41.1 Interface Event Triggers .............................................................................................. 85
5.41.2 Key Triggers................................................................................................................. 86
5.41.3 Message Handling Triggers ......................................................................................... 86
5.41.4 Navigational Triggers .................................................................................................. 86
5.41.5 Transactional Triggers................................................................................................. 87
5.41.6 Validation Triggers ...................................................................................................... 89
5.42 Validation ........................................................................................................................... 90
5.43 WINDOW_HANDLER.......................................................................................................... 91
5.44 Window Standards ............................................................................................................ 92
5.44.1 Adding the Toolbar to your Window .......................................................................... 92
5.44.2 Pop-Up Windows ........................................................................................................ 93
6 Pro*C........................................................................................................................................... 95
6.1 General Standards ................................................................................................................ 95
6.2 Include files ........................................................................................................................... 95
6.3 #define section ..................................................................................................................... 96
6.4 Program structure ................................................................................................................. 96
6.4.1 main() ............................................................................................................................. 96
6.4.2 init() ................................................................................................................................ 96
6.4.3 process() ......................................................................................................................... 96
6.4.4 final() .............................................................................................................................. 97
6.5 Program layout...................................................................................................................... 97
6.6 Variables................................................................................................................................ 97
6.6.1 Number as String............................................................................................................ 97
6.6.2 Identifiers vs. Quantities ................................................................................................ 97
6.6.3 Date ................................................................................................................................ 98
6.6.4 Variables Types and Naming .......................................................................................... 98
6.6.5 Exception Handling Variables and Macros .................................................................... 99
6.7 Function prototype ............................................................................................................... 99
6.8 Return Codes and Values ...................................................................................................... 99
6.9 Capitalization ...................................................................................................................... 100
6.9.1 Functions ...................................................................................................................... 100
6.9.2 Variables....................................................................................................................... 100
6.9.3 Macro Substitutions ..................................................................................................... 100
6.9.4 Oracle Reserved Words................................................................................................ 100
6.10 Brackets ........................................................................................................................... 101
6.11 Comments ....................................................................................................................... 102
6.12 Handling Errors and Logging Messages ........................................................................... 102

© 2006 Enabler Page 5


Development Standards
6.12.1 The Daily Log File ...................................................................................................... 102
6.12.2 LOG_MESSAGE() ....................................................................................................... 102
6.12.3 The Program Error File .............................................................................................. 103
6.12.4 WRITE_ERROR() ........................................................................................................ 103
6.12.5 Error Handling After a SQL Statement ...................................................................... 103
6.12.6 Error Handling After a PL/SQL Block ......................................................................... 104
6.13 Conditional, Sequential Control and Loops ..................................................................... 105
6.13.1 If ................................................................................................................................ 105
6.13.2 GoTo.......................................................................................................................... 105
6.13.3 While Loop ................................................................................................................ 106
6.14 Cursors and Database Interaction ................................................................................... 106
6.15 Indicator Variables ........................................................................................................... 107
6.16 Array Processing .............................................................................................................. 109
6.17 Memory Allocation .......................................................................................................... 110
6.18 Restart Recovery and Multi-Threading............................................................................ 111
6.19 Standard Calls .................................................................................................................. 112
7 Appendix A: Development Check-list ....................................................................................... 116
7.1 PL/SQL programs ................................................................................................................ 116
7.2 Oracle Forms ....................................................................................................................... 117
7.3 Pro*C ................................................................................................................................... 118
7.4 Shell Script .......................................................................................................................... 119
8 Appendix B: Package Example .................................................................................................. 122
9 Appendix C: Pro*C Example...................................................................................................... 124

© 2006 Enabler Page 6


Development Standards

2 Introduction
This document details the coding standards that should be followed when changing the existing
programs or creating new ones in Oracle Retail application.
It will ensure that code is written and maintained to specific standards.

It has been created as part of the Enabler’s project and is intended for use by internal Enabler,
contractors, and third parties developing or modifying code on behalf of Enabler.

All new objects developed for specifc clients must have their prefix “XX” to be easily identified as
customized objects. This rule is applied only to new objects, i.e. existing object cannot have the
name changed.

© 2006 Enabler Page 7


Development Standards

3 DDL Statements
DDL statements or events include ALTER, CREATE and DROP actions. The rules below
should be used:
• All DDL statements should be coded to ensure that indentation is aligned so that statements
are easily readable;
• DDL and DML statements should never be mixed in a script;
• A DDL script should only execute DDL against one table. If additional tables need to be
amended, additional scripts should be written;
• No needs for commit statements as these are implicit in DDL.
Alter table example:
alter table ADDR drop constraint <PREFIX>_ADR_STA_FK;

Create table example:


create table <PREFIX>_BUYER
(
BUYER NUMBER(4) not null,
EMAIL VARCHAR2(100)
);

Create index example:


create index <PREFIX>_ORDHEAD_I1 on ORDHEAD (VENDOR_ORDER_NO, CONTRACT_NO);

© 2006 Enabler Page 8


Development Standards

4 DML Statements
DML statements are common to both SQL, and PL/SQL modules, which include INSERT, UPDATE,
DELETE and SELECT commands. For the coding of these commands, the following standards should
be adhered to:
• DML and DDL statements should never be mixed in a script;
• A DML script should only INSERT/UPDATE/DELETE into one table. If additional tables need to be
amended, additional scripts should be written;
• At the end of the DML statement, it should be included the COMMIT statement.

4.1 Select
• Each column or field in the statement should occupy its own line, and when there is more than
one table in the FROM clause, prefixed with the table alias;
• Each table in the FROM clause should occupy its own line, and when there is more than one
table, an alias assigned to each;
• Ensure that the order of columns in the SELECT and FROM clause is similar;
• Ensure that each statement in the WHERE clause occupies its own line, unless wrapping is
required. In this case, indent to make the wrapping obvious;
• Ensure that both the SELECT, FROM, WHERE, ORDER BY and GROUP BY clauses are aligned to
improve readability.
Example:
select im.item,
im.dept,
il.location
from item_master im,
item_loc il
where im.item = il.item
and im.dept = 200
order by im.item,
im.dept;

4.2 Insert
• Any insert statement should have the column names specified after the table name, and the
alignment should be easily readable;
• In multi-line inserts, it may be more beneficial to include a number of columns on a line, which
should be at the discretion of the developer;
• Each column should occupy its own line after the initial column, and when more than one table
appears in any clause, the column should be prefixed with the table alias.
Example:
insert into chain (chain,
chain_name,
mgr_name,

© 2006 Enabler Page 9


Development Standards
currency_code)
values (1,
'Chain Name’,
'Chain Manager',
'USD');

4.3 Update
• Ensure that indentation is aligned so that statements are easily readable, as in the following
example;
• Ensure the where condition is added if applicable.
Example:
update ordloc
set qty_ordered = qty_ordered + L_value,
estimated_instock_date = TO_DATE(L_date)
where order_no = L_order_no
and item = L_item
and location = L_location;

4.4 Delete
• It is discretionary whether the developer specifies “FROM” before the table name in the DELETE
clause, but it is not required;
• Ensure that indentation is aligned so that statements are easily readable, as in the following
example;
• Ensure the where condition is added if applicable.
Example:
delete ord_temp
where ord_temp_seq_no = L_order_no
and item = L_item;

© 2006 Enabler Page 10


Development Standards

5 SQL*Loader
• For the coding of SQL*Loader scripts (.ctl), both the input file (data) and the bad (reject) file
should be specified;
• Positions of each field should be specified, where file format is fixed, where the field starts and
ends within the data file;
• Note that the log file created after executing the SQL*Loader script will be of the same name as
the data file, but suffixed with .log;
• Ensure that indentation is aligned so that statements are easily readable.
Example:
-- Header/Comments section (must be prefixed by –- for comment)
LOAD DATA
INFILE '<prefix>_store.dat'
BADFILE '<prefix>_store.bad'

APPEND
INTO TABLE store_grade_store

(store_grade_group_id POSITION (1:1) INTEGER EXTERNAL,


store POSITION (2:5) INTEGER EXTERNAL,
store_grade POSITION (6:10) CHAR
)

© 2006 Enabler Page 11


Development Standards

6 PL/SQL programs
Here you will find information on the technical aspects of using PL/SQL programs to store program
components of the Oracle Retail application on the database server.

6.1 General Standards


If functions are designed and coded correctly, the potential for reusability is very high. Functions
should be broken down into the smallest logical sub-function for maximum reusability (i.e.
GET_VDATE). If all reusable functions are created as such, testing becomes simplified as well.
For the modules to be reusable they must be designed around some kind of framework that is
consistent in how it determines what constitutes a module. To begin with, we group related
functions into packages. So, all functions that deal with validating stores will be placed in the
STORE_VALIDATE_SQL package.
Avoid writing very large PL/SQL modules that contain many SQL statements. It is more beneficial
to write a number of functions, and access those functions from within the main package body.
This also helps with re-usability, resulting in less code having to be maintained.

6.1.1 Indentation
The code should be indented in 3 positions. Do not use TAB, instead of use 3 spaces. Code should
be indented so that it is easily readable and the columns should be aligned.

6.1.2 Naming Conventions


The package name should accurately describe what the general action is for all functions within
the package. Each object area (store, department, supplier, etc.) will always contain an attribute
package and a validation package, such as SUPPLIER_ATTRIBUTE_SQL and
SUPPLIER_VALIDATE_SQL. All other packages should be named to reflect the object and action, for
example, PACKITEM_WH_CREATE_SQL package will tell you from the name that all functions
within this package will deal with creating new pack item and warehouse relationships.
With a naming convention defined that describes what is being done in the system, it can be seen
that straight-forward coding standards for calling these functions will greatly improve code
readability, among other things. Standards for packages themselves will improve error handling
and allow developers to call functions without having to understand every detail of the function
being called.

6.1.3 Fully Document the Specification


Each variable, procedure, and function defined in a package’s specification should be fully
documented in the specification, discussing what the component does. A user should never have
to look at the package body to understand what a function or variable is used for. This allows
someone to quickly examine the specification and determine whether an existing function meets
their needs or not. It is imperative that the documentation is well written and detailed.

© 2006 Enabler Page 12


Development Standards

6.1.4 Close All Cursors


It is vital that all cursors are closed in database functions. This means if an error occurs the cursor
must be closed before the function is exited:
if C_MY_CURSOR%NOTFOUND then
close C_MY_CURSOR;
O_error_message := SQL_LIB.CREATE_MSG('INV_DATE','10-JUN-20','20');
return FALSE;
else
close C_MY_CURSOR;
end if;

6.1.5 Calling SQL Packages Functions from a Form


To call an SQL function from a form we use the following syntax (for a detailed explanation of the
syntax, see the section in Forms Coding Standards on Calling a Function from a Form):
if DEPT_ATTRIB_SQL.GET_DEPT_NAME(L_error_message,
:B_dept.dept,
:B_dept.TI_dept_name) = FALSE then
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;

If you call a function that has an insert, update, or delete statement in it, additional code must be
added to handle rolling back to the appropriate spot if the function fails. To do this, a savepoint
should be created just prior to the call to the function, and a rollback added for failure conditions:
Issue_Savepoint('A');
if ORDER_VALIDATE.ORDER_NUMBER(L_error_message,
:block.item1, ...) = FALSE then
Issue_Rollback('A');
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;

Note that you should not use Savepoint A in place of Issue_Savepoint('A') or either Rollback to A or
Rollback Work to A in place of Issue_Rollback('A').
Also note that while the rollback will undo changes to the database, it will not revert variable
values passed into the function to their previous values. That is, if a function changes one of the
:block.item or other variable values passed into it as an IN OUT variable before raising an
exception and returning FALSE, those values already changed will retain their changed values (this
is standard behavior for functions in C as well).

6.1.6 No Commits, Posts or Savepoints in Functions


All commit logic should be handled from within batch programs or forms, never in server
functions.

6.1.7 Calling a Function from Another Function


The following logic cascades any error messages generated in the called function right out of the
nested SQL functions:
if PACKAGE.FUNCTION(O_error_message,
[other parms]) = FALSE then

© 2006 Enabler Page 13


Development Standards
return FALSE;
end if;

6.1.8 No Reference to :block.item in Server Functions


Your package will not even compile if you try this. You cannot reference any block.item within a
server function because it is not aware of your form, only the variables you pass it.

6.1.9 Package Size


Neither the package spec nor the body should individually exceed 1200 lines. As a general rule, try
to stay below 1000, and in no case should 1500 be exceeded.

6.1.10 User Separators


Before and after every function within a package a separator line should be placed. A separator
line is a line of dashes (---). This makes it easy to identify where individual functions begin and end.

6.1.11 Clarify your ENDs


The end statement in every procedure and function should read:
END PROGRAM_UNIT_NAME;

This helps identify what procedure or function you are looking at.

6.2 PL/SQL Objects


6.2.1 Function
Function created in Oracle Retail should always return a BOOLEAN value:
• TRUE if the function executed successfully;
• FALSE if the function failed.
An exception for this rule happens when the function is called by a process that cannot handle the
Boolean value, but only character or numeric. But for functions used in Oracle Forms, Database or
Pro*C programs, it should be used only return BOOLEAN.
The last line of a function, before the exception handling, should be return TRUE.
You must not use the return value of the function to indicate whether a cursor is found or any
other information. These indicators require additional parameters to be evaluated after successful
execution of the function has been determined. Using the return value to indicate information
other than the function’s success would restrict its reusability.
Example:
FUNCTION CLOSE_TSF(O_error_message IN OUT VARCHAR2,
O_closed IN OUT BOOLEAN,
I_tsf_no IN APPT_DETAIL.DOC%TYPE)
RETURN BOOLEAN IS
<variable/exception/cursor declarations>
BEGIN
<main body of code – cursor use etc>
return TRUE;
EXCEPTION

© 2006 Enabler Page 14


Development Standards
WHEN OTHERS THEN
<assign error message and context etc>
return FALSE;
END CLOSE_TSF;

6.2.2 Procedures
Procedures should be created in Oracle Retail only in particular cases. Whenever possible, it
should be used Functions.
Example:
PROCEDURE NEXT_MERCH_NUMBER(O_merch_number IN OUT NUMBER,
O_return_code IN OUT VARCHAR2,
O_error_message IN OUT VARCHAR2) IS
<variable/exception/cursor declarations>
BEGIN
<main body of code – cursor use etc>
EXCEPTION
WHEN OTHERS THEN
<assign error message, return code and context etc>
END NEXT_MERCH_NUMBER;

6.2.3 Packages
Avoid coding standalone functions or procedures, if they are related and can be included in a
package. Packages are a useful way of storing multiple functions, procedures, collections and
external procedures. Note that if the package is new, the function name inside should not start
with the <prefix>.
Example of package specification:
CREATE OR REPLACE PACKAGE <PREFIX>_DEAL_SQL AS
< Include here the comments using the standard defined for the client >
--------------------------------------------------------------------------
-- Function: GET_DEAL_DATES
-- Purpose : Get the deal active and close dates
--------------------------------------------------------------------------
FUNCTION GET_DEAL_DATES(O_error_message IN OUT VARCHAR2,
O_active_date IN DEAL_HEAD.ACTIVE_DATE%TYPE,
I_close_date IN DEAL_HEAD.CLOSE_DATE%TYPE
I_deal_id IN DEAL_HEAD.DEAL_ID%TYPE)
RETURN BOOLEAN;
--------------------------------------------------------------------------
END <PREFIX>_DEAL_SQL;

Example of package body:


CREATE OR REPLACE PACKAGE BODY <PREFIX>_DEAL_SQL AS
--------------------------------------------------------------------------
FUNCTION GET_DEAL_DATES(O_error_message IN OUT VARCHAR2,
O_active_date IN DEAL_HEAD.ACTIVE_DATE%TYPE,
I_close_date IN DEAL_HEAD.CLOSE_DATE%TYPE,
I_deal_id IN DEAL_HEAD.DEAL_ID%TYPE)
RETURN BOOLEAN IS
<Declaration section>
BEGIN
<Function logic>
return TRUE;
EXCEPTION
<Exception handling>

© 2006 Enabler Page 15


Development Standards
END GET_DEAL_DATES;
--------------------------------------------------------------------------
END <PREFIX>_DEAL_SQL;

6.2.4 Triggers
• Avoid using INSTEAD OF triggers, unless absolutely necessary, as the logic can become
confusing and difficult to support;
• Ensure that a trigger is essential for the capturing of information, as they can have a
performance impact (especially if poorly-written!);
• If a trigger does not need to be continually enabled, ensure necessary steps are in place to
disable (for example during the overnight batch, if applicable).
Example:
CREATE OR REPLACE TRIGGER <PREFIX>_ITEM_LOC_AIUR
AFTER INSERT OR UPDATE
ON ITEM_LOC
FOR EACH ROW
DECLARE
<variable/exception/cursor declarations>
BEGIN
<main body of code – INSERTS etc>
EXCEPTION
WHEN OTHERS THEN
<assign error message and context etc>
END <PREFIX>_ITEM_LOC_AIUR;

6.2.5 Types
• For creation of a type within a package header, variables should be named and indentation
should be easily readable, as in the examples below;
• Where appropriate, anchor definitions to column datatypes, rather than hard coding.
Example of type creation within a package header:
CREATE OR REPLACE TYPE BOL_VIRTUAL_LOC IS RECORD
(
vir_from_loc ITEM_LOC.LOC%TYPE,
vir_from_loc_type ITEM_LOC.LOC%TYPE,
vir_qty ITEM_LOC_SOH.STOCK_ON_HAND%TYPE,
);
TYPE bol_virtual_loc_array IS TABLE of BOL_VIRTUAL_LOC
INDEX BY BINARY_INTEGER;

Example of object type creation, not in a package:


CREATE OR REPLACE TYPE <PREFIX>_DEAL_COMP_PROM_REC AS OBJECT
(
promotion_id NUMBER(10),
promo_comp_id NUMBER(10),
contribution_pct NUMBER(12,4)
);

Finally, for collection types, the name should end in _TBL, for example an Oracle Retail collection
types such as DATE_TBL or COUNTRY_TBL.

© 2006 Enabler Page 16


Development Standards

6.3 Package Variables


Variables should be declared at beginning of each module. Assuming a package body contains
multiple functions or procedures, each should have a clear label variable section.
Global variables
Global variables should always be named “GP_<variable_name>”. These variables are declared in
a package spec and are available globally to all functions open anywhere in the same database
session, not just those in the same package. They can be referenced by
PACKAGE_NAME.VARIABLE_NAME. It is acceptable to give these variable intuitive names in the
spec since it will be clear that a spec variable is being referenced just by context.
Package variables
Package variables should always be named “LP_<variable_name>”. These variables are internal to
the package body but global to more than one function. They are declared in a package body
outside of any function and are available globally to all functions below the variable declaration.
They retain their values once initialized for a given database session.
Local variables
Local variables should always be named “L_<variable_name>”. These variables are declared in the
beginning of the function/procedure and are available only in the function where are created.
Variable Scope
Declared inside a Package Spec
(Global Variable, prefix = GP)
PACKAGE SPEC

Declared inside a Package Body


(Package Variable, prefix = LP)
PACKAGE BODY OTHER PACKAGES

Declared inside a Function


(Local Variable, prefix = L)
FUNCTION
OTHER PACKAGES

Declared inside a Function


(Local Variable, prefix = L)
FUNCTION
OTHER PACKAGES

Variables should be always anchored to a table column datatype, to ensure that the code is
“futureproof”. For example, the L_item variable would be anchored to item column of the
ITEM_MASTER table, such as:
L_item ITEM_MASTER.ITEM%TYPE;

© 2006 Enabler Page 17


Development Standards
If there are no appropriate columns to reference, ensure that an appropriate datatype is used,
with precision specified, for example:
L_return_code VARCHAR2(5);

Assuming the initial value of a variable is known, or is to remain static, the variable should be
assigned that value in the variable declaration section. For example:
L_program VARCHAR2(64) := 'ITEM_NUMBER_SQL.GET_NEXT';

When declaring multiple variables, ensure that each “column” is lined up, for ease of readability,
for example:
L_item ITEM_MASTER.ITEM%TYPE;
L_dept ITEM_MASTER.DEPT%TYPE;
L_class ITEM_MASTER.CLASS%TYPE;

6.4 Defining Input, Output and In/Out Variables


Within your functions and procedures you will be accepting variables and using variables to return
values.
All input variables should be explicitly declared as IN variables and the variable name should be
preceded with an I.
Example: I_last_date IN DATE
All output variables should be explicitly declared as IN OUT variables and the variable name should
be preceded with an O. It is imperative that output variables be declared as IN OUT otherwise the
procedure or function will not accept :block.item values from Oracle Forms.
Example: O_last_date IN OUT DATE
Any variable used for input and output should be declared as an IN OUT variable and the variable
name should be preceded with an IO.
Example: IO_last_date IN OUT DATE
All parameters should be declared with the %TYPE format so that table changes will automatically
be cascaded through the system.
Parameters should be listed in the following order: First O_error_message, then all output
parameters, followed by input output parameters, and finally all input parameters. This standard
is important because it makes it easier to add new input parameters without having to change old
function calls.
CREATE OR REPLACE PACKAGE NEWPACKAGE_SQL AS
FUNCTION NEWFUNCTION(O_error_message IN OUT VARCHAR2,
O_item IN OUT ITEM_MASTER.ITEM%TYPE,
IO_unit_cost IN OUT ORDLOC.UNIT_COST%TYPE,
I_store IN STORE.STORE%TYPE)
RETURN BOOLEAN IS

6.5 Exception and Error Handling


Exception handling is an essential part of writing a PL/SQL module, and exceptions should always
be handled. Any PL/SQL package, standalone function, procedure, trigger or any other type should
include exception handling, with at least a WHEN OTHERS section, and also a meaningful error
regarding the data context when the exception occurred.

© 2006 Enabler Page 18


Development Standards
Note that exceptions should be used for handling a failure within PL/SQL, and not routine logic.
For example, avoid executing an insert statement, checking for an exception, and then performing
an update as a result of the exception.
When an error occurs within a PL/SQL module, ensure that the exceptions are raised. The
exception error message must always be able to provide the point at which the code failed, and
the data context.
The first parameter of all functions within an SQL package should be a VARCHAR2 of the name
O_error_message. If at any point the SQL fails, O_error_message should be set to the error
message, and a return code of FALSE should be returned.
All PL/SQL code should have at least the following exception handling:
EXCEPTION
when OTHERS then
O_error_message := SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,
<PACKAGE_NAME.FUNCTION_NAME>,
SQLCODE);
return FALSE;
END <function_name>;

The function SQL_LIB.CREATE_MSG takes four parameters and produces an encoded string that
emessage, wmessage, etc. will automatically decode and display properly. The first parameter is
the message code. The other 3 are parameter to the used in the error message. All available
messages are stored on the RTK_ERRORS table. Use the Message Search Tool to find existing
messages and to add new messages to the RTK_ERRORS table.
There are two ways to handle errors that happen in the code:
1. Return FALSE directly in the code. In some cases where the SQL fails (for example if a
NOTFOUND is true which indicates an essential piece of data is missing) all cursors should be
closed, O_error_message should be set to the error message, and a return code of FALSE
should be returned.
Example:
if C_MY_CURSOR%NOTFOUND then
close C_MY_CURSOR;
O_error_message := SQL_LIB.CREATE_MSG('INV_DATE',I_date);
return FALSE;
else
close C_MY_CURSOR;
end if;

2. Create an exception and handle the error in the exception section.


Example:
FUNCTION <function_name> (in/out parameters)
RETURN BOOLEAN IS
L_program VARCHAR2(64) := <function_name>;
<variable/exception/cursor declaration, for example>
e_item_failure EXCEPTION;
BEGIN
<main body of code>
raise e_item_failure <if named exception encountered>
return TRUE;

© 2006 Enabler Page 19


Development Standards
EXCEPTION
WHEN e_item_failure THEN
O_error_message := SQL_LIB.CREATE_MSG(<error_code>,
SQLERRM,
L_program,
SQLCODE);
return FALSE;
EXCEPTION
when OTHERS then
O_error_message := SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,
L_program,
SQLCODE);
return FALSE;
END <function_name>;

Note that exceptions should always be named “e_<variable_name>”, unless they are standard
Oracle exceptions. Ensure that the exception section is easily readable and clear.

6.6 Conditional and Sequential Control


Conditional control statements are essential in PL/SQL, and care should be taken to ensure they
are coded to a standard. Always ensure that statements are aligned, as appropriate.
Here is an example of an if-then combination:
if <condition> then
<Executable statements>
end if;

Here is an example of an if-then-else combination:


if <condition> then
<TRUE executable statements>
else
<FALSE/NULL executable statements>
end if;

Here is an example of an if-then-elsif combination:


if <condition 1> then
<Statement 1>
elsif <condition n> then
<Statement n>
else
<else statement>
end if;

Sequential control statements include GOTO and NULL. The GOTO statement should NOT be used
as it can become very difficult to follow and support. The NULL statement is a viable executable
statement that does nothing, so it should be avoided as well.

6.7 Loops
There are a number of loops that can be used within PL/SQL, and there are benefits for using each.
Loop choice is at the developer’s discretion, but careful consideration should be given to ensure
optimum performance.

© 2006 Enabler Page 20


Development Standards
• Simple LOOP
Ensure that a simple loop is exited upon meeting a specified condition, otherwise it will be an
infinite loop (either by specifying and EXIT or EXIT WHEN statement). To write a loop that will
repeat until a condition is met can be done using the EXIT WHEN condition after any executable
statements.
The syntax use is shown below, and the indentation should be followed:
LOOP
<Executable statements>
END LOOP;

• Numeric FOR LOOP


Loop iterates through a number series, and the lowest and highest numbers can be variables,
with the use of REVERSE to start with the highest. The syntax is shown below, and the
indentation should be followed:
FOR loop_index IN [REVERSE] <lowest_number..highest_number>
LOOP
<Executable statements>
END LOOP;

• Cursor FOR LOOP


PL/SQL automatically declares the loop index a record of cursor_name%ROWTYPE, so it does
not require variable declaration. The cursor will automatically be opened, rows fetched and
then cursor closed. Use this cursor rather than specifying OPEN-FETCH-CLOSE statements.
The syntax is shown below, and the indentation should be followed (note that it assumes
declaration of a cursor prior to execution):
FOR rec IN <cursor_name>
LOOP
<Executable statements>
<Reference rec.columns as declared in cursor>
END LOOP;

• WHILE LOOP
The while loop should be used when a cursor only requires execution if a given condition is
met, or may be occurring. The syntax is shown below, and the indentation should be followed:
WHILE <condition>
LOOP
<Executable statements>
END LOOP;

6.8 Cursor Standards and Use


All queries should be handled using cursors. The cursor should be created in the declaration
section, just after the local variable declaration. Some rules should be followed:

© 2006 Enabler Page 21


Development Standards
• Cursor naming convention should be C_<NAME>, where name is appropriate to the function of
the cursor. For example, to get item location information, the cursor may be named
C_GET_ITEM_LOC;
• Avoid naming cursors C1, C2 and so on, as this is not helpful and takes longer for someone
reading the code;
• When the query returns only one record, use open-fetch-close commands to execute the
query;
• If executing open-fetch-close cursors, always ensure that the cursor is closed, even if an
exception occurs;
• When the query returns more than one record, use FOR LOOP command to execute the query;
• When needed, define cursor parameters for the cursor, which assists reusability. These should
be named P_<column> and anchored to a specific column datatype, where possible;
• The use of cursor attributes is discretionary, but may be required by the developer. Attributes
include %ISOPEN, %FOUND, %NOTFOUND and %ROWCOUNT;
• The for update of clause is associated with a cursor, and must be included as the last line in a
cursor declaration. Columns referenced in the select clause cause the rows to be locked, but
any joined tables are not locked as a result;
• The where current of clause should be used where appropriate, as this saves repeating the
where clause of a select statement. It can be used with delete and update statements, and will
delete or update the current row, identified by the cursor.
Example 1:
cursor C_GET_PACK_IND is
select pack_ind
from item_master
where item = I_item;
...
open C_GET_PACK_IND;
fetch C_GET_PACK_IND into L_pack_ind;
close C_GET_PACK_IND;

Example 2:
cursor C_ITEM_LOC (P_item ITEM_LOC.ITEM%TYPE) is
select loc,
loc_type
from item_loc
where item = P_item
for update nowait;
...
FOR rec in C_ITEM_LOC LOOP
if rec.loc_type = 'S' then
<Executable statement 1>
else
<Executable statement 2>
end if;
END LOOP;

© 2006 Enabler Page 22


Development Standards

6.9 Record Locking


Within Oracle, if an update or delete occurs and another user has that record locked, by default
the process will wait indefinitely for the lock to be released. This can result in what appears to be
the system hanging. To alleviate this problem online we manually lock all records before an
update or delete.
All hard updates and deletes (update and delete statements) must be locked manually. This does
not include updates and deletes in base table blocks in a Form since Oracle locks those
automatically for us.
By manually locking the records, instead of the application freezing up when User B attempts to
update or delete a record that User A is updating or deleting, Oracle Retail will display a helpful
message specific to which record on which table is locked and User B can take action accordingly.
This logic should be inside packages, not in your Form.
NOTE: No special logic needs to be added for base table items in Forms; this is handled
automatically by Forms.
Prior to every hard update or delete, for example:
update item_supplier
set unit_cost = L_unit_cost
where item = I_item
and supplier = I_supplier;

ensure that the following occur:


Step 1: Create the following local variables.
Create the following in the declaration section:
L_table VARCHAR2(30);
RECORD_LOCKED EXCEPTION;
PRAGMA EXCEPTION_INIT(Record_Locked, -54);

Step 2: Declare a locking cursor.


Declare a cursor in which you select 'x' from the table on which you will perform a delete or
update with the same where clause as the one in the update or delete statement with for update
nowait added to the end:
cursor C_LOCK_ITEM_SUPPLIER is
select 'x'
from item_supplier
where item = I_item
and supplier = I_supplier
for update nowait;

Make certain that the where clause for this cursor is the same as the where clause in your update
or delete statement. Make sure to include the for update nowait; line. This is the command that
locks the record (the keyword nowait tells ORACLE not to wait if the table has been locked by
another user).

© 2006 Enabler Page 23


Development Standards
If multiple tables are queried, you can use for update of <column_name> nowait to confine row
locking to a particular table. The column name used should be a column found only on the table
that you want to lock. This will prevent the statement from locking all tables involved in the where
clause.
Step 3: Open and close the locking cursor before your update/delete statement.
Immediately before the update or delete statement, perform an open and close of the cursor to
lock all rows within the cursor:
L_table := 'ITEM_SUPPLIER';
open C_LOCK_ITEM_SUPPLIER;
close C_LOCK_ITEM_SUPPLIER;

update item_supplier
set unit_cost = L_unit_cost
where item = I_item
and supplier = I_supplier;

Note: All rows are locked when you open the cursor, not as they are fetched. If you have
update/delete statements that affect multiple rows, you need to open and close the C_LOCK
cursor only once since the rows stay locked throughout the execution of your function/procedure.
Step 4: Declare a RECORD_LOCKED exception.
You may pass up to two of the key values from the where clause of your locking cursor into the
error message to help identify which record(s) is locked.
EXCEPTION
when RECORD_LOCKED then
O_error_message := SQL_LIB.CREATE_MSG('TABLE_LOCKED',
L_table,
TO_CHAR(I_item),
TO_CHAR(I_supplier));

To test whether the locking cursor is set up correctly you will need to open two sessions (Forms,
SQL*Plus, etc.). In the first session, update or delete the test records without committing. It is
critical that the records are in limbo, waiting to be either committed or rolled-back. It is at this
point that the records are locked. Now test your record locking code. If everything is functioning
correctly you should not be permitted to update or delete the records and should be given a
message indicating the records are locked by another user.

6.10 Performing Tuning


The following preferences should be borne in mind when coding SQL statements. Please note that
this list is not exhaustive, and contains a number of basic hints.

6.10.1 General Standards


• All transaction logic (SQL) must reside in packages on the server;
• All packages containing SQL must reside on the server;
• Limit table joins to 4 tables;
• Join on primary and foreign keys only;
• Avoid joining a table to itself;

© 2006 Enabler Page 24


Development Standards
• Columns used in the join must be of the same data type and length;
• Never use SELECT *;
• Use joins instead of sub-queries;
• Use IN and BETWEEN instead of >= and <= and instead of OR;
• Avoid the use of NOTs;
• ORDER BYs should be performed on indexed columns only;
• Don't put a wild card in the first position of a LIKE statement;
• Avoid putting a wild card in the middle position of a LIKE statement;
• Use if exists statements rather than if select count(*) > 0;
• ALWAYS use explicit cursors for select statements.

6.10.2 UNION vs UNION ALL


Where possible use UNION ALL instead of UNION. Using UNION results in less efficient queries, as
duplicates are stripped out by Oracle using the DISTINCT command.

6.10.3 IN vs EXISTS
In certain circumstances, IN is more efficient than EXISTS, and if the selective predicate is in the
subquery, use IN. Alternatively, when the selective predicate is in the parent query, use EXISTS.
The example shows where the EXISTS would be more efficient:
SELECT im.item,
im.dept
FROM item_master im
WHERE im.dept = 200
AND im.item_level = im.tran_level
AND EXISTS (SELECT 1
FROM item_loc il
WHERE im.item = il.item);

Use of the IN statement instead would be more efficient in the following example:
SELECT im.item,
im.dept
FROM item_master im
WHERE im.item IN (SELECT il.item
FROM item_loc il
WHERE il.loc_type = 'W');

6.10.4 NOT EXISTS vs NOT IN


Using NOT EXISTS within a SQL statement may be preferable to using NOT IN. Writing a query that
has a WHERE clause with NOT IN specified would negate use of an index. Alternatively, a similar
SQL query using a NOT EXISTS should use the index based on the contents of the subquery.
Optimizer hints such as access paths (ROWID, INDEX), join method, join order, hints (e.g. Rule
based usage) should be made use of. Also, ensure when specifying hints on tables that use an
alias, that the alias name is used in the hint clause (otherwise it will be ignored).

© 2006 Enabler Page 25


Development Standards
Performance tools such as explain plan, tkprof and SQL trace should be used to ensure queries are
coded efficiently. Ensure that amount of data in tables being used is appropriate, and will give an
indication of performance.

© 2006 Enabler Page 26


Development Standards

7 Oracle Forms
Here you will find information on how to properly code a Form to Oracle Retail standards. This
includes technical information on how to properly implement common functionality.

7.1 General Standards


• Forms must only contain GUI logic. GUI logic includes everything that manipulates the screen,
such as enabling/disabling items, navigation, setting the value of a field, etc. All database
transactions (all DML statements – insert update, delete) will reside in packages on the server.
This means no select statements, no inserts, no updates and no deletes. All of these types of
DML statements should reside in packages on the server.
There are some situations where it is acceptable some DML directly in the forms. For example,
if there is the need to do a simple select to validate some data or a simple
insert/update/delete. If a completely new database object needs to be create only for these
simple DML, it is preferable include this DML directly in the forms instead of creating a new
object in the database. Note that it is applied only for simple statements. If it is complex
statement, where the performance is something to be considered, it must be done in the
database.
• When referencing a field fully qualify the reference. That is, always use :Block.Item rather than
just :Item.
• Indent 3 spaces within each conditional statement or loop (Do not use TAB, instead of use 3
spaces positions):
if L_store != L_transfer_loc then
do something here;
else
if L_quantity is NOT NULL then
do something else;
end if;
end if;

• Indent all DML statements to right-align leftmost words:


select store_name
from store
where store = 1
and store_desc = 'Minneapolis';

• Comments must use the -- format and be indented to the same level as the code. Do not use
multiple line comments with /* and */. Also, do not append comments to the end of code on
the same line as the code itself:
Wrong:
Next_Item; /* do not use these types of comments */

Right:
-- This type of comment should be used

© 2006 Enabler Page 27


Development Standards
-- even when the comment spans multiple lines.
Next_Item;

• Use %TYPE when declaring a local variable that holds data which resides on a table, such as
ITEM:
DECLARE
L_item ITEM_MASTER.ITEM%TYPE;

• When performing an INSERT, always include the column names:


insert into store (store,
store_name)
values (1,
'Minneapolis');

• Hard coded values are never acceptable. This includes values, such as using the number 1000 in
your code to indicate corporate zone. This also includes any text, such as 'Retail Markup %'.
Codes or variables should be used to indicate or retrieve all such values.
• Use Parameters only for values being passed between forms. Use internal variables for form
level variables that are referred throughout the form (like system option fields). Always
question internal variables that seem suspect. These are usually quite easy to detect by name
and usually a sign of some bad coding going on. Some examples of poor Parameter use, just to
give you an idea what to look for, would be: PM_inserted, PM_record, PM_valid,
PM_deleted_only_rec1, PM_p_get_item_field.

7.2 Alerts & emessage


Alerts are the pop-up dialogue windows that prompt the user to respond to a question. Alert
message types are stored in the reference Form FM_REFER with the following names:
ALT_ERROR ALT_YES_NO_CANCEL ALT_YES_ALL_NO
ALT_WARNING ALT_YES_NO
ALT_INFO ALT_OK_CANCEL

The method for displaying alerts is simple. Use the following Oracle Retail library functions for
displaying messages to the user:
• emessage for errors (red circle with white X icon)
• wmessage for warnings (exclamation point yield sign icon)
• imessage for information (blue “i” in a white circle icon)
• F_YES_NO gives the user a choice of YES or NO buttons
• F_YES_NO_CANCEL gives the user a choice of YES, NO or CANCEL buttons
• F_OK_CANCEL gives the user a choice of OK or CANCEL buttons
• F_YES_NO_ALL gives the user a choice of YES, NO or ALL buttons
When using emessage, F_YES_NO, or any other alert-displaying function in which a message is
involved, it is imperative that message keys from RTK_ERRORS be used, not hard-coded messages.

© 2006 Enabler Page 28


Development Standards
All available messages are stored on the RTK_ERRORS table.
Note the different results of coding the following 3 lines. The standard way to enter an alert
message is case 2.
1. emessage ('Invalid Warehouse entered.');
2. emessage ('INV_WH');
3. emessage ('inv_wh');
Case 1
If the enclosed text is of mixed case and > 25 characters in length, emessage will automatically
substitute it for the alert text without attempting to search for the text as a Key. Therefore, the
resulting ALT_ERROR alert message would read: "Invalid Warehouse entered." This is no longer an
acceptable option because it cannot be translated into multiple languages, but it is important to
realize that alert messages function this way.
Case 2
If the enclosed text is all upper case and < 25 characters in length, emessage will automatically
search for 'INV_WH' as a key. If it is found in the key column, the resulting ALT_ERROR alert
message would read: "Invalid Warehouse entered." If emessage cannot find INV_WH in the key
column, the alert message will read: "INV_WH".
Case 3
If the enclosed text is of lower or mixed case, emessage will automatically substitute it for the
alert text without attempting to search for the text as a KEY. Therefore, the resulting ALT_ERROR
alert message would read: "inv_wh".

7.3 Block Types and their Triggers


There are five main block types used in the Oracle Retail applications that are built with Oracle
Forms:
1. Single Record Blocks
2. Multi-Record Blocks
3. Action Block
4. Find Forms
5. Apply Blocks
For each type of block, there will be a specific set of triggers that should be used to accomplish the
task at hand.
Each block of a specific type should always use the same triggers to accomplish the same task. This
keeps our forms very maintainable and ensures that they are functioning properly and are
performance-conscious.

7.4 Form Level Triggers


All Forms, regardless of the types of blocks they contain, will have the following Form level triggers
and code:
WHEN-NEW-FORM-INSTANCE
This trigger should always contain a call to the program unit P_FORM_STARTUP.
WHEN-WINDOW-ACTIVATED

© 2006 Enabler Page 29


Development Standards
This trigger should always contain a call to WINDOW_HANDLER.WWA. It may contain additional
code as necessary. Always look out for performance issues as this trigger fires frequently.
WHEN-WINDOW-CLOSED
FM_TEMPL contains standard logic in this trigger and commented-out instructions for how to
modify it. Always be careful in this section to make sure it handles all windows within your form.
ON-ERROR & ON-MESSAGE
These triggers should contain standard error handling calls. If additional messages are ever hard-
coded into an ON-ERROR trigger the purpose should be clearly documented within the trigger.
KEY-EXIT
This trigger should contain only the line
Execute_Trigger('WHEN-WINDOW-CLOSED');
which will fire the logic in the WHEN-WINDOW-CLOSED trigger. Changes should only be made to
WHEN-WINDOW-CLOSED, not KEY-EXIT.
KEY-LISTVAL
Standard List of Values code. Currently checks to make sure you are on a valid type of item before
issuing the command.
KEY-NEXT-ITEM & KEY-PREV-ITEM
Standard navigation commands.
KEY-OTHERS
Should always contain NULL.

7.5 Single Record Blocks

7.5.1 Block Level Triggers


The only block level triggers that should be required in single record blocks are PRE/POST QUERY,
PRE/POST INSERT, PRE/POST UPDATE, and PRE/POST DELETE.
PRE-QUERY is used to set the where clause.
POST-QUERY is used to populate non-base table fields and potentially enable/disable menu
options.

© 2006 Enabler Page 30


Development Standards
PRE-INSERT should be used for any functionality that must be performed before the record is
inserted. An example of this would be the attribution of the SYSDATE to the field
CREATE_DATETIME.
POST-INSERT, similarly, should be used for any functionality that must be performed after the
record is inserted. An example of this would be inserting records into VAT_ITEM once a record is
written to ITEM_MASTER.
PRE-UPDATE/POST-UPDATE serves the same purpose as the PRE/POST INSERT triggers.
PRE-DELETE/POST-DELETE also serves the same purpose as the PRE/POST INSERT triggers. Please
note, however, that if any additional deleting is performed in either of these triggers, the locking
cursor should reside in the actual Delete button, not here. If the locking cursor is not in the button
and the record is locked, the record will be cleared from the form, but will remain in the database.

7.5.2 Item Level Triggers


Depending on the type of item, the valid triggers that belong at the item level are WHEN-
VALIDATE-ITEM, WHEN-LIST-CHANGED, WHEN-CHECKBOX-CHANGED, WHEN-RADIO-CHANGED,
WHEN-BUTTON-PRESSED, KEY-LISTVAL, KEY-F2, KEY-NEXT-ITEM and KEY-PREV-ITEM.
WHEN-VALIDATE-ITEM is used to validate the value entered into a particular field. Validation, as
well as enabling and disabling fields, is the type of logic that belongs in this trigger. All WHEN-
VALIDATE-ITEM triggers should call procedures in the FORM_VALIDATE package.
Validation code must always first check that the field being validated is NOT NULL. If the field is
NULL, sometimes a corresponding description can also be set to NULL. A check should always be
done to ensure that the description field is not NULL already before setting it to be NULL, to avoid
the :system.record_status being set to 'CHANGED'.
All fields Required property should be set to NO on the Property Palette (except for some list
boxes). Validation of required fields does not take place until the user tries to save the form
(usually in P_CHECK_REQUIRED).
WHEN-LIST-CHANGED, WHEN-RADIO-CHANGED, WHEN-CHECKBOX-CHANGED and WHEN-
BUTTON-PRESSED perform specific functions relating to an action.
KEY-LISTVAL is used when the field has a list of values specified for that field, or if it has a calendar
button attached. For list of value fields, the description field and the actual item should both be
linked to the LOV. The KEY-LISTVAL for the description should do a Go_Item to the main item and
perform a Do_Key('List_Values') – see the LOV section.
KEY-F2 should be specified on all ITEM, UPC, and VPN fields for the item widget to work correctly.
This will be automatically attached (even though it does not show up) through the PC_ITEM
property class. The other fields will need to have it added manually.
KEY-NEXT-ITEM and KEY-PREV-ITEM should only be specified if absolutely necessary. If they are
used, then Validate(Item_Scope) should be placed at the beginning of the trigger if necessary.

7.6 Multi Record Blocks

© 2006 Enabler Page 31


Development Standards

7.6.1 Block Level Triggers


The triggers on the multi-record blocks are the same as the triggers on the single-record blocks
with a few exceptions.
WHEN-NEW-RECORD-INSTANCE should be used for multi-record blocks. This trigger is used to
check the :system.record_status and enable/disable items appropriately using the RWIDGET
package.
if :system.record_status IN ('NEW', 'INSERT') then
RWIDGET.TURN_ON('B_czonegrp.zone_group_id');
RWIDGET.TURN_ON('B_czonegrp.cost_level');
elsif :system.record_status in ('’QUERY', 'CHANGED') then
RWIDGET.TURN_OFF('B_czonegrp.zone_group_id');
RWIDGET.TURN_OFF('B_czonegrp.cost_level');
end if;

WHEN-VALIDATE-RECORD should verify that each required item has a value entered for the
current record. All fields’ Required property should be set to NO on the Property Palette to permit
us to make this check for required fields ourselves. If REQUIRED = YES, then Forms takes over
these NULL checks for us. That would be fine except Forms gives a generic message which reads:
'Field Must Be Entered' – it doesn't tell the user which field must be entered. So, we bypass Forms'
check and create our own with specific error messages.
Because a Go_Item cannot be performed from within this trigger, if an item is NULL the message
that is displayed should state the name of the item that needs to have a value. The WVR trigger
should not contain any ‘if all fields are NULL’ or ‘if all fields are NOT NULL’ logic. That kind of logic
does not belong in a WVR trigger.
BEGIN
if :B_main.store is NULL then
emessage('STORE_REQ');
raise FORM_TRIGGER_FAILURE;
end if;

© 2006 Enabler Page 32


Development Standards
END;

PRE-INSERT, which is also used for single record blocks, has an additional purpose for multi-record
blocks. This trigger should perform a check for duplicate records. This is in addition to the checking
done in the WHEN-VALIDATE-ITEM trigger. The only time the check in PRE-INSERT will be caught is
when the user enters two new records with the same key within the same session.

7.6.2 Item Level Triggers


The item level triggers are the same as the single-record block item level triggers with the
exception of button triggers, detailed below.

7.6.3 Buttons in Multi-Record Blocks


The WHEN-NEW-RECORD-INSTANCE trigger will not work correctly for buttons in a multi-record
block since the button can be clicked before they can be disabled appropriately (the WHEN-
BUTTON-PRESSED trigger fires before the WHEN-NEW-RECORD-INSTANCE trigger).
To rectify this, a check of the :system.record_status should be made in the WHEN-BUTTON-
PRESSED trigger. If the record should be disabled (if the :system.record_status is 'QUERY' or
'CHANGED'), a Go_Item should be performed in the WHEN-BUTTON-PRESSED sending the cursor
to the neighboring field. The Go_Item command will cause the WHEN-NEW-RECORD-INSTANCE
trigger to fire, disabling fields appropriately.
Here is an example of code in an LOV’s WHEN-BUTTON-PRESSED trigger in a multi-record block:
BEGIN
if :system.record_status IN ('CHANGED','QUERY') then
Go_Item('B_czone.currency_code');
raise FORM_TRIGGER_FAILURE;
end if;
---
Go_Item('B_czone.currency_code');
Do_Key('List_Values');
EXCEPTION
when FORM_TRIGGER_FAILURE then
raise;
when OTHERS then
emessage(SQLERRM);
raise FORM_TRIGGER_FAILURE;
END;

7.6.4 Date Push Buttons


If there is a date button in the multi-record block, validation will be performed on the entire
record when the date button is clicked because the date widget pulls up a different block (Forms
navigates to the date widget’s block). This can cause problems if some required fields have not
been filled for a record (including the date). To alleviate this problem, use
Set_Form_Property(form_name, VALIDATION, FORM_SCOPE) immediately before calling
P_CALENDAR to temporarily prevent validation. Be sure to set validation back to DEFAULT after
the calendar command and in the error handling for the trigger.
BEGIN
Set_Form_Property('FM_test', VALIDATION, FORM_SCOPE);
P_CALENDAR.SHOW_CALENDAR(L_return_to => L_return_to …
Set_Form_Property('FM_test', VALIDATION, DEFAULT);

© 2006 Enabler Page 33


Development Standards
EXCEPTION
when FORM_TRIGGER_FAILURE then
Set_Form_Property('FM_test', VALIDATION, DEFAULT);
raise;
when OTHERS then
emessage(SQLERRM);
raise FORM_TRIGGER_FAILURE;
END;

7.6.5 Multi-Language Considerations for Multi-Record Blocks


Forms with multi-record blocks have only 2 modes associated with them (VIEW/EDIT). In this
scenario, Edit mode implies that users can add records and can edit existing records. This has
special implications for secondary language users.
One assumption made with multiple languages is that no records can be added if you are a
secondary language user. This is not enforced throughout the system. It is assumed that all data
stored on tables will be in the primary language. For forms with only 2 modes, we are unable to
make proper use of the _TL field since it would mean dynamically turning this field on and off
based on the record. To avoid this problem, we enforce the assumption that secondary language
users cannot add records in these types of forms.
To do this, we do a check in P_FORM_STARTUP to see if the user is a secondary language user. If
so, we set INSERT_ALLOWED to NO for the block and turn on the _TL field. The Add button will still
be enabled, but will display the 'NO_ADD_RECS_NOT_PRIM_LANG' message when clicked.
The workaround for users is to change their user preferences temporarily to the primary language,
enter the data in the primary language, and then change back to the preferred language.

© 2006 Enabler Page 34


Development Standards

7.7 Apply Blocks

Apply blocks are generally used when the user needs to apply a group of items at one time (such
as an item list, or a region of stores). There are many different examples of apply block
implementation currently within the ORMS. The apply block should always mimic the multi-record
block. The apply block’s data should be populated in the multi-record block’s WHEN-NEW-
RECORD-INSTANCE trigger.
When you click the Add button, a new record should be available in the apply block for you to
enter.
When you click Delete, the existing record is deleted or cleared as appropriate.
When you click Apply, values are copied from the Apply block into the multi-record block and a
Post is issued (along with the command WINDOW_HANDLER.SET_FORM_CHANGED – See the
section on WINDOW_HANDLER for additional information).
When lists of things, such as an item list, are applied, the Apply button would call a function to
perform inserts into the table. Then it would perform a POST, set the window handler to changed,
and then re-query the block.

7.8 Action Blocks

The common buttons used for a single record block are OK, OK+Repeat, Delete, and Cancel.

© 2006 Enabler Page 35


Development Standards

7.8.1 OK
This button should call the procedure P_EXIT.SAVE_FORM. This procedure will typically call
P_CHECK_REQUIRED to check for required fields. Usually, a Commit_Form is then issued.
In some cases, checks are made to see if the :system.form_status is anything other than 'QUERY'
before a commit is performed. This should NOT be done because of the possible use of posting
within the form.
In addition, because of posting within forms, the WINDOW_HANDLER.SET_FORM_UNCHANGED
should be set in P_EXIT.SAVE_FORM to verify that window closing logic will still work if the OK +
Repeat button has been clicked. See the section on WINDOW_HANDLER for additional
information.

7.8.2 OK + Repeat
This button should call the procedure P_EXIT.SAVE_FORM(FALSE), with the FALSE being passed to
stop the procedure from exiting the form. Then it should perform a Clear_Form, and usually will
call P_FORM_STARTUP again to reset the form appropriately.
For View/Edit modes, when this button is enabled, some special processing must occur. For
example, on a form such as department, when OK + Repeat is clicked, the dept field will be
enabled. Then, if a department is chosen via an LOV (or a department is entered in the field
manually) a query should be performed. That means, for this one instance only, that an
Execute_Query would be performed in a KEY-NEXT-ITEM trigger.
Note: This is the only code that really belongs in the KEY-NEXT-ITEM. Validation will still belong in
the WHEN-VALIDATE-ITEM trigger. Other than this one instance, only navigation belongs in
navigational triggers.

7.8.3 Delete
See “Delete Button Standard Code”.

7.8.4 Cancel
This button should call the P_EXIT.CANCEL_FORM procedure, which generally should just contain
an Exit_Form(No_Validate) command.
Any forms that have dependencies for parent/child relationships should ideally be in one form so
that this program unit does not need to deal with deleting incomplete records. This is not true in
all cases, however, so this may contain delete logic and committing as well.

7.8.5 LOV Buttons and Calendar Buttons


These buttons can also be defined within the action block. The only function these buttons should
perform is to do a Go_Item to the appropriate field and then do a Do_Key command as
appropriate.

© 2006 Enabler Page 36


Development Standards

7.9 Find Blocks

A find form is different from other forms in that there should be no commit logic within the form.
It is used as a means to access other forms.
Find forms work by allowing users to enter in the values they wish to limit the where clause by and
then searching on those values. Technically, this is done by dynamically setting the where clause.
This is done in the P_SET_WHERE_CLAUSE program unit.

© 2006 Enabler Page 37


Development Standards
P_SET_WHERE clause is a program unit that structures the where clause. There is a 2000-character
limit on the where clause, so take note of how long yours could possibly be when writing this
procedure.
Translation is probably the trickiest part of this procedure. For any description being searched on,
there must be two select statements. One is for selecting the value, and one is for selecting the
value doing a join with the TL_SHADOW table if the user is a secondary language user (see
Multiple Language Support for details).
Because no commit logic is being done in these forms, many people assume that translated values
can be placed directly in base table fields. As far as data integrity goes, this is a safe assumption.
For record locking purposes, however, this should never be done.
All base table description fields should be translated in a non-base table field and populated in the
POST-QUERY trigger.
These features of find forms frequently get neglected from a testing standpoint: the ‘Save
Defaults’ and ‘Restore Defaults’ menu options. Save Defaults can be used to save search criteria by
user name. Restore Defaults will restore these search criteria if new criteria have been entered.
The saved defaults will always appear when the find form is initially opened. The Refresh button
will always refresh, also based on these saved defaults.
The find.pll library has some functions in it to make the coding of find forms much easier. One
function is the SET_SCREEN.REFRESH. This should be called behind the Refresh button and will
perform the appropriate refresh action for your find form. The only way this function will work
appropriately, however, is if all of the naming standards for find forms are followed exactly. This
means that your search criteria must be in a block called B_main, your action block must be
B_action, your buttons are named as expected, and your action list box is B_main.LI_action.

7.10 Boilerplate Text


Boilerplate Text is no longer used at Oracle Retail. All instances in which Boilerplate Text is needed
requires that Display Items be created instead.
To create Display Items which will act as Boilerplate Text in your Form follow these steps:
1. Create a new Display Item (as opposed to a Text Item) in whichever block makes the most
logical sense. This is likely to be the block which contains the item to which your Display Item
will refer.
2. Change the Display Item’s name to be DI_<name> where <name> represents what the
boilerplate text specifies (e.g. DI_store_name).
3. Set the following properties on the Display Item:
• Maximum Length: set to be at least double the length that is required by your Initial Value so
that the item will not be too small should translated text be a greater number of characters
• Initial Value: set to be the text you wish to have displayed
• Database Item = NO
• Bevel = NONE
• Visual Attribute Group: either BOILERPLATE_TEXT or BOLD_TEXT depending on how you
wish the text to be displayed
• Height and Width: set to fit the text of the field. Wherever space allows, make the field
longer than is actually needed to better accommodate translated values.

© 2006 Enabler Page 38


Development Standards

7.11 Busy Cursors


Any task that takes longer than two seconds must display a BUSY cursor. To code a busy cursor use
the Set_Application_Property built-in as illustrated in the following code:
BEGIN
Set_Application_Property (CURSOR_STYLE, 'BUSY');
if LANGUAGE_SQL.GET_CODE_DESC(L_error_message,
'LABL',
'MON',
:B_head.loc_desc) = FALSE then
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;
Set_Application_Property (CURSOR_STYLE, 'DEFAULT');
EXCEPTION
when FORM_TRIGGER_FAILURE then
Set_Application_Property (CURSOR_STYLE, 'DEFAULT');
raise;
when OTHERS then
emessage(SQLERRM);
Set_Application_Property (CURSOR_STYLE, 'DEFAULT');
raise FORM_TRIGGER_FAILURE;
END;

It is important to note that the busy cursor must be returned to DEFAULT in the exception
handling routine. If this code is not present, Windows will leave the busy mouse pointer as the
primary mouse pointer until the system is rebooted or a Set_Application_Property is encountered
that changes the cursor back to the default style. Any additional program units that are called
after the cursor style is set to busy must include the setting of the cursor style to default in their
exception handling as well.

7.12 Calling a Function from a Form


To call a Function we must first understand how the specific Function operates (i.e. What inputs is
the Function looking for? What outputs will the Function give us?). We can learn a great deal
about a Function by describing it in SQL*Plus (remember that we must use the
PACKAGE.FUNCTION syntax):
SQL> desc BUYER_ATTRIB_SQL.GET_BUYER_NAME

FUNCTION BUYER_ATTRIB_SQL.GET_BUYER_NAME RETURNS BOOLEAN


Argument Name Type In/Out Default?
-------------------- ----------------- ----------------
O_error_message VARCHAR2 IN/OUT
O_buyer_name VARCHAR2(32) IN/OUT
I_buyer NUMBER(4) IN

Above, we see that the Function has one input, two outputs, and RETURNS a BOOLEAN variable.
To call this Function, then, we will need to pass in one input (a buyer number) and deal with two
outputs (the buyer name and the error message). In addition, we will need to evaluate the
contents of the BOOLEAN RETURN.

© 2006 Enabler Page 39


Development Standards
To execute a Function, the name of the Function must be used in a context in which it is evaluated.
That is, we must programmatically inquire about the Function’s value. One way to evaluate a
Function would be to use it in an if/end if statement:
if MY_FUNCTION(argument list) = TRUE then
perform some task;
end if;

Another way to evaluate (and therefore execute) a Function is to assign its value to a variable:
L_some_variable := MY_FUNCTION(argument list);

In both cases, as the line of code is read, the Function will be executed. In both cases the value
that will be used in the expression is the value RETURNed by the Function. If MY_FUNCTION ends
up RETURNing the value TRUE, then the two expressions above would read like this:
if TRUE = TRUE then
perform some task;
end if;

and
L_some_variable := TRUE;

This code below is an example of calling the Function BUYER_ATTRIB_SQL.GET_BUYER_NAME


described above. We get the input that we need (a buyer number) from an item on a block and
pass it into the function. The Function sends its O_buyer_name output back to another block.item.
For the error message we need to create a local variable. Note that the inputs and outputs must
be used in the same order as they appear when describing the Function in SQL*Plus:
DECLARE
L_error_message VARCHAR2(255);
BEGIN
if BUYER_ATTRIB_SQL.GET_BUYER_NAME(L_error_message,
:B_buyer_info.TI_buyer_name,
:B_buyer_info.buyer_no) = FALSE then
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;
EXCEPTION
when FORM_TRIGGER_FAILURE then
raise;
when OTHERS then
emessage (SQLERRM);
raise FORM_TRIGGER_FAILURE;
END;

Again, Functions RETURN a value apart from the argument list (apart from the inputs and outputs).
At Oracle Retail, the RETURN is always a BOOLEAN variable holding the value TRUE if the Function
executed successfully and FALSE if the Function encountered an error.
When you call a Function, the Function call itself holds that RETURN value. For example, in the
code above, you write:
if BUYER_ATTRIB_SQL.GET_BUYER_NAME(argument list)..

© 2006 Enabler Page 40


Development Standards
As the code is executed, BUYER_ATTRIB_SQL.GET_BUYER_NAME takes on the RETURN value of
TRUE or FALSE, depending-upon what happens inside the Function.
BUYER_ATTRIB_SQL.GET_BUYER_NAME becomes a BOOLEAN variable for that moment. If the
Function executes successfully, then the result is that the lines of code in your Form read as
follows:
if TRUE(L_error_msg,
:B_buyer_info.TI_buyer_name,
:B_buyer_info.TI_buyer_no) = FALSE then
emessage(L_error_msg);
raise FORM_TRIGGER_FAILURE;
end if;

Since TRUE does not equal FALSE, the error message isn’t printed and the Exception isn’t raised.
The Function still executes, however, taking the buyer number in and passing out the buyer name.
If the Function failed then the opposite would happen:
if FALSE(L_error_msg,
:B_buyer_info.TI_buyer_name,
:B_buyer_info.TI_buyer_no) = FALSE then
emessage(L_error_msg);
raise FORM_TRIGGER_FAILURE;
end if;

Since FALSE does equal FALSE, the error message is printed, and the exception is raised.
The odd aspect to all of this is that regardless of whether the Function returns TRUE or FALSE, the
inputs are passed into the Function and the outputs are returned from the Function. So, as seen
above, no matter what happens, the Buyer Number (which is held in the item
:B_your_block.TI_buyer_no) goes into the Function, and the Buyer Name comes back from the
Function and is placed in the item :B_your_block.TI_buyer_name.
At Oracle Retail we use the syntax illustrated above when calling a Function, checking for FALSE
and calling exception handling if needed. Checking for TRUE actually results in more lines of code
and an increase in nested logic:
Right:
if BUYER_ATTRIB_SQL.GET_BUYER_NAME(L_error_message,
:B_buyer_info.TI_buyer_name,
:B_buyer_info.buyer_no) = TRUE then
do some stuff;
else
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;

Wrong:
if BUYER_ATTRIB_SQL.GET_BUYER_NAME(L_error_message,
:B_buyer_info.TI_buyer_name,
:B_buyer_info.buyer_no) = FALSE then
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;

do some stuff;

© 2006 Enabler Page 41


Development Standards

7.13 Canvas Properties


All canvases must now have a window that is attached to the canvas.
The toolbar canvas should always be attached to the main window of the Form – this should be
taken care of for you by the template Form FM_templ.

7.14 Capitalization Standards


• Oracle Forms built-ins should be referred to with the first letter of each word capitalized with
the rest in lowercase.
Example: Go_Item, Next_Record, Execute_Query, Commit_Form, Do_Key

• Objects defined within Oracle Forms should have the prefix to the object capitalized and the
rest of the name lowercase.
Example: B_shipment.TI_loc_name, W_shipment

• When referring to parameters, the word PARAMETER should appear in all capital letters.
Example: :PARAMETER.PM_mode

• System variables should always be referred to in all lowercase letters.


Example: :system.message_level, :system.form_status

• The following PL/SQL reserved words should always be all capital letters:

AUTONOMOUS_TRANSACTION EXIT SYSDATE


BEGIN FALSE TRUE
BEFORE FOR USER
BOOLEAN FORALL USING
COMMIT LOOP VARCHAR2
CONSTANT NOT WHILE
CREATE NULL %BULK_ROWCOUNT
DATE NUMBER %FOUND
DECLARE OTHERS %ISOPEN
DO POST %NOTFOUND
END PRAGMA %ROWTYPE
END LOOP ROLLBACK %ROWCOUNT
EXCEPTION SAVEPOINT %TYPE
EXEC SQL SQLCODE
EXECUTE IMEDIATE SQLERRM

• The following PL/SQL reserved words should always be all lowercase letters:

© 2006 Enabler Page 42


Development Standards
and for update of return
as from select
close goto set
connect by group by start with
current of if then
cursor insert union
delete into union all
elsif is update
end if open values
fetch or when
for update nowait raise where

• Program units, packages, stored procedures, functions, and library program units should be
referred to in all capitals with the exception of emessage, wmessage, and imessage.
Example: P_FORM_STARTUP, GET_VDATE, F_SHOW_LOV

• Cursor names should always be in all caps.


Example: open C_STORE_EXISTS, cursor C_STORE_EXISTS is

• Exception names should always be in all capitals.


Example: OTHERS, FORM_TRIGGER_FAILURE, DUP_VAL_ON_INDEX

• When using the Set_xx_Property or Get_xx_Property built-ins, the property and the value
should both be all capitals.
Example: Set_Block_Property('B_shipment', UPDATEABLE, PROPERTY_TRUE)

• ORACLE function should be all capitals.


Example: NVL(), MAX(), SUM(), EXISTS(), IN(), TRIM(), LPAD(), COUNT()

• Local variables should begin with an “L_” and the rest should be lowercase.
Example: L_store_name

• Global variables should begin with “GV_” and the rest should be lowercase.
Example: GV_store_name, GV_promotion

7.15 Closing a Window


Put all Form exiting code in the P_EXIT package. This package should have at least two procedures:
SAVE_FORM and CANCEL_FORM. SAVE_FORM should be called by the OK button and should
contain all of the logic necessary to commit the Form. CANCEL_FORM should be called by the
cancel button and should contain all the necessary logic to cancel the Form.

© 2006 Enabler Page 43


Development Standards
All other window closing logic within the Form should go in the P_EXIT package as well. Each
window should have a procedure to save and cancel and should be named SAVE_<WINDOW> and
CANCEL_<WINDOW> as appropriate.
The WHEN-WINDOW-CLOSED trigger should contain the standard code contained in the template
Form. If a Form has more than one window the code here will need to be modified following the
commented-out instructions in the trigger. Regardless, be sure to remove comments from this
trigger that are there simply to provide instructions to help you code it correctly.
Secondary windows will not be able to have the question asked as to whether or not changes
should be saved if you click on the in the upper-right hand corner because there is only one set
of save and cancel variables for the entire Form. So, within the WHEN-WINDOW-CLOSED trigger
you will need to specify what happens if the user chooses to close a secondary window (usually
the cancel functionality). See the section on WINDOW_HANDLER for details.
The KEY-EXIT trigger is part of FM_TEMPL and should only contain the following line of code:
Execute_Trigger('WHEN-WINDOW-CLOSED');

7.16 Currency
7.16.1 About this Topic
This section describes design and technical implementation of currency in Oracle Retail.
Definitions
There are five different currency types used in Oracle Retail:
• Order currency: Associated with the Ordering dialog, the order currency will in many instances
be the same as the supplier’s preferred currency. The actual cost of an order will always be
stored in the order currency, implying payment in that currency.
• Contract currency: Associated with the contract. Serve as the default currency for an order
when the order is created from a contract.
• Supplier currency: Associated with the Ordering and Supplier Maintenance dialogs, this is the
supplier’s preferred currency and will be used as the default currency on an order placed with
the supplier. Order cost is stored in the supplier currency at the ITEM level.
• Primary currency: Referenced throughout the system, this is the corporate level preferred
currency and is stored on the System Options table. This is the currency in which the corporate
level reporting is done. Primary currency values will often be a calculated or converted value
from another currency. Use the currency package to get this value.
• Local currency: Referenced throughout the system at the location level, this is the currency
used at a specific location. Stores, for example, may each have their own preferred currency. All
location specific reporting can be done in the local currency. Sales and inventory are all valued
in the local currency, making this the most accurate financial measure at any specific location.
Primary currency values are converted from the local currency using the exchange rates and
rules stored in the ORMS. Note that currency is also stored on the Price Zone table and that all
stores within a zone must share the same currency.
General Currency Rules

© 2006 Enabler Page 44


Development Standards
• Views can be toggled between currencies (local or primary). This is generally under the Options
– Currency menu selection or controlled by a set of radio buttons on the form. (See Currency
Menu Standards).
• Currency formatting in single row blocks is based on currency specific format strings stored in
the Currency table. (See Format Mask Standards).
• Currency formatting in multi-row blocks is defaulted (using the currency property classes).
There is no difference between currencies. (See Format Mask Standards).
• When a currency value is converted to another currency, it is automatically rounded to the
number of decimal places as defined in the Currencies table.
• A converted currency value is always display-only. Only the currency values actually stored on
the table are ever editable.
• Currency conversion is always done by the same package (CURRENCY_SQL package).
• Primary currency is set on the System Options table.
• Generally one currency type (local, order, primary etc.) is stored on all tables with the exception
of stock ledger and some ordering tables. Refer to the data dictionary to determine which type
is used for any particular column.
• The system can operate using a consolidated rate or operational rate (set in system options).
• The Multi-Currency flag is on the System Options table.
• No currency symbols are used in currency format strings. Use the standard international
currency code.
• Costs and Retail are stored at the store/warehouse level in the local currency.
• Price pointing is table-driven and is currency-specific.
• Corporate Pricing (Corp. Zone Group) cannot be performed in a multiple currency environment.
This cannot even be added as a zone group on zonehead.
Currency Conversions
Conversions between two explicit currency codes (i.e. USA to CAN, MEX to JAP etc.) is done using
CURRENCY_SQL.CONVERT package. The other type of conversion is between two locations using
CURRENCY_SQL.CONVERT_BY_LOCATION. Here, the in and out locations can be one of the
following: supplier, order, contract, store, warehouse, or zone. Then, for the in and out location
type, the appropriate one-letter code must be supplied: V for supplier, O for order, C for contract,
S for store, W for warehouse, or Z for zone. For a conversion to or from a zone, the zone group
must also be specified.
Finally, if the in or out location needs to be primary currency, leave the location, location type, and
the zone group fields blank. The function will then automatically convert to the primary currency.
For more information on how the packages work and specific parameters, refer to the package’s
specification.

© 2006 Enabler Page 45


Development Standards

7.16.2 Technical Implementation


Most forms that show a monetary amount should have the ability to show that value in a different
currency. The ability to view other currency values is through a menu option. This section
describes the standard for implementing menus.
Currency Menu Standards
For the currency modifications, if a form does not currently have a menu, DO NOT create a new
menu for the currency conversion options. Attach the menu 'currview' in the form level
properties. This menu has six options, Primary, Local, Supplier, Order, Order/Location, and
Contract.
Not all options will apply to the form where the menu is being attached, so remove the options
from the menu that are not needed. The following code will perform this task. Place this code in
P_FORM_STARTUP. If the Retailer is not using multiple currencies, do not display the Options
menu at all.
if multi_currency_ind = 'Y' then
-- shut of individual menu options that do not apply
Set_Menu_Item_Property('currency_menu.primary',DISPLAYED,
PROPERTY_FALSE);
Set_Menu_Item_Property('currency_menu.local', DISPLAYED,
PROPERTY_FALSE);
Set_Menu_Item_Property('currency_menu.supplier', DISPLAYED,
PROPERTY_FALSE);
Set_Menu_Item_Property('currency_menu.order', DISPLAYED,
PROPERTY_FALSE);
Set_Menu_Item_Property('currency_menu.orderlocal', DISPLAYED,
PROPERTY_FALSE);
Set_Menu_Item_Property('currency_menu.contract', DISPLAYED,
PROPERTY_FALSE);
else
Set_Menu_Item_Property(‘main_menu.options’, DISPLAYED,
PROPERTY_FALSE);
end if;

If the form has an existing menu, add the Currency menu under the Options menu with the
appropriate currency types (Primary, Local, etc.) Then, in P_FORM_STARTUP, include the following
code:
if multi_currency_ind = 'N' then
Set_Menu_Item_Property('options_menu.currency', DISPLAYED,
PROPERTY_FALSE);
end if;

The menu options that remain enabled expect certain triggers to be present. For each option that
your Form will have, create its associated trigger at the Form level:
Option Expected Trigger Name
Primary T_primary
Local T_local
Supplier T_supplier
Order T_order
Order/Local T_orderlocal
Contract T_contract

Currency Fields

© 2006 Enabler Page 46


Development Standards
When adding the ability to switch between different currency types the recommended approach is
to include one field for each currency type. These fields will be stacked on top of one another.
Take the form Retail by Zone (itemretail) as an example. This form has item_zone_price as the
base table currency column. The Unit Retail field, which is the base table field, holds the value
from the table, but there is also a field Unit Retail Prim placed directly on top of Unit Retail that
holds the unit retail value in the primary currency. The Unit Retail Prim field is populated in the
POST-QUERY on the block level and in the WHEN-VALIDATE-ITEM trigger for the Unit Retail item.
So, as the Unit Retail value changes, the Unit Retail Prim value changes as well. Then, when the
user selects Options – Currency from the menu to view the retail cost in the currency type they
want, RWIDGET.DISPLAY_ON and RWIDGET.DISPLAY_OFF can be used to show the appropriate
field.
Multiview Standards
The approach that will be used to implement multiple currencies in Multiview forms is as follows.
This only applies to Multiview forms where the user can view currency fields in two different
currency types.
1. Create a duplicate field for each currency value and currency code.
Ex. :store.unit_cost :store.unit_cost_prim
The duplicate field will be identical, but not a base table field and will be display-only at all
times.
When the duplicate fields are added to the multiview tool, the col_order should be grouped
together.
Ex. unit_cost 3, unit_retail 4, currency_code 5, unit_cost_prim 6, unit_retail_prim 7,
currency_code_prim 8
2. Create new Display Item fields to hold the column headings for each new converted currency
field. By naming each field with local/primary/etc., the user will be able to see which currency
types are available.
Ex. Unit Cost will now be renamed Prmry Unit Cost and Local Unit Cost will be named Local Unit
Cost
3. If the multiview displays multiple currencies in the local values, add the Local Currency Code
field to the default multiview.
4. Create a Second saved multiview that will be a duplicate of the default multiview but, instead
of the local currency fields, replace them with the primary currency fields.
5. The post query should populate the local_currency_code, primary_currency_code and
prim_unit_cost, prim_unit_retail, etc. This will be done with a series of package calls to
CURRENCY_SQL.GET_CURR_LOC, SYSTEM_OPTIONS_SQL.CURRENCY_CODE and
CURRENCY_SQL.CONVERT.
6. New triggers should be created at the form level titled T_VIEW_PRIMARY and T_VIEW_LOCAL.
T_VIEW_PRIMARY will be executed when the user selects Currency – Primary from the Options
menu. This trigger will load the saved multiview that displays the primary values.
T_VIEW_LOCAL will load the default multiview. This will allow the user to toggle between local
and primary without hitting the database multiple times.

© 2006 Enabler Page 47


Development Standards
Example:
BEGIN
Validate(Item_Scope);
if Form_Success then
P_MULTIVIEW.LOAD_SET(3);
end if;
END;

Format Mask Standards


All currency fields should still have a property class attached to them, which will use the attached
format mask as the default format mask.
Single record blocks
Using the currency package, override the default currency format mask based on the currency that
is being displayed. As the user switches currencies based on the menu, switch the format masks
accordingly.
Multi-record block
Any time all the records show one currency type (all the values are in primary currency), override
the format mask in the property class by using the appropriate package to fetch the format mask
and then use Set_Item_Property to set the mask. If the multi-record block is showing different
currencies (all the values are in local, which can be different), then DO NOT override the property
class format mask.

7.17 Delete Button Standard Code


In Oracle Retail Forms, there is standard code that exists behind all Delete buttons.
Regardless of block type, a check must be performed to ensure that the cursor is on the correct
block – the block containing the data to be deleted. This check is made by looking at the value of
:system.current_block. Note that all :system variables hold their data in ALL CAPS. This means any
comparisons made to :system variables must be performed using ALL CAPS.
Once on the correct block, a call to the Oracle Retail function F_YES_NO passing in the message
string '?CONFIRM_DELETE' will prompt the user, asking if they are sure they want to perform the
deletion. If they choose ‘YES’, then the Forms built-in Delete_Record is performed.
After Delete_Record, the code varies based-upon whether the deletion is being performed in a
multi-record block or a single-record block.
• In a multi-record block we POST. POSTing ensures that should a user delete a record, then try
to re-add that same record in the same session, they won’t violate any key constraints. Per
Oracle Retail Standards, any time we POST, we follow the line with a call to
WINDOW_HANDLER.SET_FORM_CHANGED. This ensures that the WHEN-WINDOW-CLOSED
trigger will function correctly (see the section on WINDOW_HANDLER for details).
• In a single record block we simply call P_EXIT.SAVE_FORM. This commits the Form, then exits.
Multi-Record Block
if :system.current_block != 'B_HEAD' then
Go_Block('B_head');
end if;
if F_YES_NO('?CONFIRM_DELETE') then
Delete_Record;

© 2006 Enabler Page 48


Development Standards
POST;
WINDOW_HANDLER.SET_FORM_CHANGED;
end if;

Single Record Block


if :system.current_block != 'B_HEAD' then
Go_Block('B_head');
end if;
if F_Yes_No('?CONFIRM_DELETE') then
Delete_Record;
P_EXIT.SAVE_FORM;
end if;

Delete buttons must have the Mouse Navigate property set to NO on the property palette to
prevent validation triggers from needlessly firing. See the section on Mouse Navigate in this
document for further details.

7.18 Dynamic Hierarchy


Both the organizational and merchandise hierarchies are able to have flexible values, so that if a
client refers to what we know as “department” as “category”, they are able to enter that value
into a table and the word “category” will now be dynamically displayed everywhere the word
“department” previously was displayed.
The dynamic hierarchy is valid for the following levels of the hierarchies:
Organization:
Company (OH1)
Chain (OH2)
Area (OH3)
Region (OH4)
District (OH5)

Merchandise:
Division (MH2)
Group (MH3)
Department (MH4)
Class (MH5)
Subclass (MH6)

The decoding of hierarchy names is done through the table DYNAMIC_HIER_CODE which contains
the following fields:
RMS_NAME VARCHAR2(30)
CLIENT_NAME VARCHAR2(30)
ABBR_NAME VARCHAR2(6)

This table is pre-populated and no rows can be added. There are values on the table for each level
of the hierarchy in its singular and plural form.
The package DYNAMIC_HIER_CODE_SQL should be called to retrieve values from this table. If you
needed to find the value for the word department, you would pass the word in the case as you
want it returned (uppercase, initcap, lowercase). If there is no client value on the table, the ORMS
name will automatically be returned.
In order to support the dynamic hierarchy there are a few steps that must be followed while
developing:

© 2006 Enabler Page 49


Development Standards
• In each Form, anytime any of the hierarchy fields is referenced, it must now be retrieved from
the DYNAMIC_HIER_CODE table using the DYNAMIC_HIER_CODE_SQL. This must be done for
prompts, button text, menu items, LOV titles, LOV columns, etc. The appropriate
Set_xx_Property should be used to change the value of the field.
• For each new message that is added, if one of the hierarchies is a part of the message text, the
keyword should be replaced with the appropriate code.
Company = @OH1 Division = @MH2
Companies = @OHP1 Division = @MHP2
Chain = @OH2 Group = @MH3
Chains = @OHP2 Groups = @MHP3
etc...

• Codes will be handled in the same way as messages. New codes should use the code for the
keyword rather than the actual word.
• Multiview: When a new column is added to multiview that is one of the levels of the hierarchy,
place the appropriate code into the column pointer field on the multiview edit Form for the
specific column.
Multiview Codes
Department @MH4
Department Name @MHN4
Class @MH5
Class Name @MHN5
Subclass @MH6
Subclass Name @MHN6

In multiview, the abbreviated name will be displayed for department, class, and subclass.
If the dynamic hierarchy table is updated, a database trigger exists to automatically update the
multiview pointer column appropriately.

7.19 Editors
There are two methods to call the editor: the simple method and the slightly more complicated
method. The simple method will place a title ‘Comments’ at the top of the editor and will be in the
default position. The slightly more complicated method lets you pass in a code type and code to
use as the title and an x and y position if you desire. Use the second option if your editor is for
anything other than comments so that it can be titled appropriately.
One additional new standard: Editors should now be able to be referenced using the key-listval
trigger in all fields that have an editor attached to them.

7.19.1 OG_editor Installation Instructions


• Check a writable/no lock copy of FM_edit out of your version control tool.
• Open FM_edit in Forms Builder.
• Click on the Object Groups.
• Select the OG_Editor and drag it into the Object Groups on your form.

© 2006 Enabler Page 50


Development Standards
• Forms will ask you if you want to subclass/remove the path. Answer yes. A new referenced
window, canvas, block, procedure specs and procedure body will be attached to your form.
• Editors will now be called from the KEY-LISTVAL trigger on the item associated with the editor,
to allow keyboard access to the editor. Any code in the WHEN-BUTTON-PRESSED trigger on the
button associated with the editor should be replaced with the following code:
Go_Item('<item name>');
Do_Key('List_Values');
Where <item name> is the fully qualified block.item name of the item associated with the
editor.
• In the KEY-LISTVAL trigger of the item associated with the editor, call the package
P_EDITOR.CALL_EDITOR. You have two options as to how to call the package, the simple option
and the slightly more complicated option. Choose wisely (i.e. choose the simple option unless
you have a really good reason to do otherwise). In single record blocks, you should just need to
call the editor. Multi-record blocks with record level validation add a tiny bit of complexity and
will be discussed below.
Simple Option
Call P_EDITOR.CALL_EDITOR. Pass the item that should receive the text in as I_item. Pass either
'Y' or 'N' in as the editable_ind. Keep in mind that in different modes and under different
circumstances, you may or may not want the user to be able to edit the text in the editor. Once
installed the editor will pop up when you hit the button or the list values button (F9). It will
have a title of ‘Comments’ and be centered on the main window (unless the main window is
too small, in which case it will be in the upper right hand corner). Note that if you use this
method and things do not work right, use the slightly more complicated option. The procedure
spec is:
P_EDITOR.CALL_EDITOR(I_item IN VARCHAR2,
I_editable_ind IN VARCHAR2);

Example
I have a field called :B_head.TI_comments. :B_head.TI_comments should be enabled in 'NEW'
and 'EDIT' modes, but not in 'VIEW' mode. There is an iconic pushbutton next to
:B_head.TI_comments called :B_head.PB_comments. The KEY-LISTVAL trigger on
:B_head.TI_comments should look something like this:
if :PARAMETER.PM_mode = 'VIEW' then
P_CALL_EDITOR.CALL_EDITOR('B_head.TI_comments', 'N');
else
P_CALL_EDITOR.CALL_EDITOR('B_head.TI_comments', 'Y');
end if;

Slightly More Complicated Option

© 2006 Enabler Page 51


Development Standards
The slightly more complicated option gives you more control over the appearance of the editor.
It lets you set the editor window’s title and/or position. You should only be using this option if
you need to change either the editor window title or position. If ‘Comments’ and centered is
fine you should be using the simple option. If not, call P_EDITOR.CALL_EDITOR. Pass the item
that should receive the text in as I_item. Pass either 'Y' or 'N' in as the editable_ind. Pass in a
code_type from the code_head table (or NULL if you want the title to default to ‘Comments’).
Pass in a code from the code_detail table of that code type (or NULL if you want the title to
default to ‘Comments’). Pass in an x coordinate (or NULL if you want the position to default to
centered). Pass in a y coordinate (or NULL if you want the position to default to centered). The
procedure spec is:
P_EDITOR.CALL_EDITOR(I_item IN VARCHAR2,
I_editable_ind IN VARCHAR2,
I_code_type IN VARCHAR2,
I_title_code IN VARCHAR2,
I_x_position IN NUMBER,
I_y_position IN NUMBER);

Example
I have a field called :B_head.TI_import_description. There is an iconic pushbutton next to
:B_head.TI_import_description called :B_head.PB_desc. I want the title of the editor window to
be ‘Import Description’, but I still want the editor window to be centered over the main
window. The KEY-LISTVAL trigger on :B_head.TI_import_description should look something like
this:
PROCEDURE CALL_EDITOR('B_head.TI_import_description',
'Y',
'LABL',
'IDLB',
NULL,
NULL);

If I also want the editor window to be elsewhere over the main window, the KEY-LISTVAL
trigger on :B_head.TI_import_description should look something like this:
PROCEDURE CALL_EDITOR('B_head.TI_import_description',
'Y',
'LABL',
'IDLB',
100,
200);

• You should now be able to save, generate and run your form with an editor. If you have any
other references to the old editor left in your form, delete them.

© 2006 Enabler Page 52


Development Standards

7.19.2 Multi-Record Blocks


You can install the editor in either it’s simple or slightly more complicated form on a multi-record
block. There is, however, a minor catch. One of the most useful things about multi-record blocks is
record level validation. The WHEN-VALIDATE-RECORD trigger fires anytime you navigate out of a
record. To use the editor (or any other reusable, referenced object, like the calendar widget,
calculator widget, etc) you must navigate off the record. The work around for this problem is to
change the validation unit immediately before and after the editor call and in the exception
handling in the calling trigger.
For example, the tl_shdw form consists of a single record header block and a multi-record detail
block. The header block displays a text string to be translated. The detail block consists of a
language field, a language LOV and a translation field with an editor attached. Both language and
translated value are required fields. In the multi-record block, you enter or choose a language
from the LOV and enter a translated value for the header text string. The problem occurs when
you enter a language and decide you want to type the translated value in the editor. You press the
editor button, which calls the editor and forces you to navigate out of the record. Your record level
validation fires and tells you that you must enter a translated value. But that is exactly what you
are trying to do. Alas.
The solution to change the validation unit of the form before the call to the editor. The Validation
unit is a form level property. It specifies that scope of form validation at runtime. Specifically, the
validation unit defines the maximum amount of data that can be entered before forms initiates
validation. Our default is item level (so validation forces every time we navigate off of an item).
Changing the validation unit to form level will prevent the record level validation from firing (no
validation will fire until we try to navigate out of the form). Right after the editor call, change the
validation unit of the form back to the default (because we really do want validation to fire at the
item level – except under very special circumstances). This will cause the WHEN-VALIDATE-
RECORD to fire and validate the record appropriately. Make sure to also change the validation unit
back to default in the exception handling of the WHEN-BUTTON-PRESSED trigger to make sure that
the validation unit is set appropriately if the editor malfunctions. While this strategy seems a bit
odd, we employ it for all reusable objects (i.e. calculator, calendar etc).
So, in the multi-record block on tl_shdw, the WHEN-BUTTON-PRESSED trigger should look
something like this:
BEGIN
Set_Form_Property('TL_SHDW', VALIDATION_UNIT, 'FORM_SCOPE');
---
if :PARAMETER.PM_MODE != 'VIEW' then
P_EDITOR.CALL_EDITOR('B_detail.translated_value', 'Y', NULL, NULL,
105, 115);
else
P_EDITOR.CALL_EDITOR('B_detail.translated_value', 'N', NULL,
NULL, 105, 115);
end if;
---
Set_Form_Property('TL_SHDW', VALIDATION_UNIT, 'DEFAULT');
EXCEPTION
when FORM_TRIGGER_FAILURE then
Set_Form_Property('TL_SHDW', VALIDATION_UNIT, 'DEFAULT');
raise;
when OTHERS then
Set_Form_Property('TL_SHDW', VALIDATION_UNIT, 'DEFAULT');

© 2006 Enabler Page 53


Development Standards
emessage(SQLERRM);
raise FORM_TRIGGER_FAILURE;
END;

7.20 Form Level Properties


Set the following Form-level properties to the corresponding values:
• Form Horizontal Toolbar Canvas should be NULL.
• Console Window should also be set to NULL.
• First Navigation Block should be set accordingly.

7.21 Form_Success
Form_Success is a Function provided by Oracle Forms to test the success or failure of a Forms
Built-In (“Built-Ins” meaning Forms subprograms like Go_Block, Set_Item_Property,
Execute_Query, etc.). When a Built-In fails, a runtime error occurs and Forms issues the
appropriate error message. However, no exception is raised – the code does not jump to the
Exception section, the trigger itself does not fail, and any subsequent statements in the code are
still executed. That is why sometimes you get a string of error messages in Forms when something
goes wrong instead of a single error message.
Because processing will not automatically stop if a Built-In fails, there will be occasions in which
we need to manually halt the processing. Form_Success reports on the outcome of the most
recently executed Built-In and can be used to determine whether processing should halt or
continue.
In the following example we perform validation on a block, causing any WHEN-VALIDATE-ITEM
and WHEN-VALIDATE-RECORD triggers on the block to fire. If an error occurs in one of those
triggers the processing will not halt, but the value for Form_Success will be FALSE. To get the
processing to halt we can do as follows:
Validate(Block_Scope);
if not Form_Success then
raise FORM_TRIGGER_FAILURE;
end if;
Go_Block('B_detail');

To get the correct results you must check the value of Form_Success immediately after the Built-
In is called. That is, another line of code cannot occur between the Built-In and the testing of
Form_Success value. If another action occurs, Form_Success will not reflect the status of the Built-
In you are testing. In the example above, any line of code between Validate(Block_Scope) and if
NOT Form_Success would interfere with Form_Success being able to return the correct result (this
is true even for something as innocuous as wmessage).
Let’s look at a more detailed example. Below are three PL/SQL blocks which are executed as the
result of a button being pressed. In this example a WHEN-BUTTON-PRESSED trigger issues the
Do_Key('Next_Item') Built-In. The KEY-NEXT-ITEM trigger makes a call to a procedure named
P_TEST. P_TEST evaluates some hypothetical condition, and ends-up raising
FORM_TRIGGER_FAILURE:

© 2006 Enabler Page 54


Development Standards

Here is the process flow in detail:


WHEN-BUTTON-PRESSED
Do_Key('Next_Item');

This is a Built-In which will manually fire the KEY-NEXT-ITEM trigger.


© 2006 Enabler Page 55
Development Standards
KEY-NEXT-ITEM
P_TEST;

This is a call to run the procedure P_TEST.


P_TEST
if something goes wrong then
raise FORM_TRIGGER_FAILURE;

This manually raises an exception, causing processing to jump down to the Exception section
when FORM_TRIGGER_FAILURE then
raise;

In the Exception section, raise takes us back to the EXCEPTION section of the KEY-NEXT-ITEM
trigger (not the execution section). This means that the line P_DO_SOMETHING_ELSE in KEY-NEXT-
ITEM does not fire. Instead, we are stuck in its EXCEPTION section.
KEY-NEXT-ITEM
when FORM_TRIGGER_FAILURE then
raise;

When we raise to the WHEN-BUTTON-PRESSED trigger the unexpected happens: instead of going
to the EXCEPTION section, we stay in the execution section and actually perform the line
Go_Block('B_detail'). This is because everything began with Do_Key, which is a Forms Built-In. As
described at the beginning of this section, if a Forms Built-In causes an error to occur, processing
continues in that trigger/program unit. So, even though the subsequent blocks of code (KEY-NEXT-
ITEM and P_TEST) behaved as we would like by going into their exception-handling routines, the
WHEN-BUTTON-PRESSED trigger does not stop.
WHEN-BUTTON-PRESSED
Go_Block('B_detail');
It is here that Form_Success can be invaluable:

© 2006 Enabler Page 56


Development Standards
Even though the processing continues after Do_Key('Next_Item'), because a
FORM_TRIGGER_FAILURE was raised in P_TEST, the value of Form_Success will be FALSE. Adding a
check for Form_Success and then manually raising FORM_TRIGGER_FAILURE in this trigger will
stop the processing here, giving us the functionality we desire.
Usage Tips:
When should I use Form_Success? Clearly you can’t use it after every Go_Block, Execute_Query,
Do_Key, or other Built-In. Trapping the Form_Success is critical in many situations where an error
is generated but processing does not stop. HOWEVER, see the usage note below on COMMITting
and POSTing. Also bear in mind that a string of unhandled errors may very well be a sign of bad
code, not a sign that Form_Success should be used.
Usage Note:
Form_Success should not be used to test whether a Commit_Form or POST has succeeded.
Because Commit_Form or POST may cause many other triggers to fire, when you evaluate
Form_Success it may not reflect the status of Commit_Form (or POST) but of some other, more
recently executed Built-In. A more accurate technique is to check that the :system.form_status
variable is set to 'QUERY' after the operation is done. See the section on POSTing in Forms and the
section on :system Variables in this document for details.

7.22 FORM_TRIGGER_FAILURE
FORM_TRIGGER_FAILURE is a built-in Oracle Forms Exception type that stops the processing in the
trigger. At Oracle Retail, whenever an anticipated Exception occurs in Forms, you print an error
message to the screen with emessage, then manually call FORM_TRIGGER_FAILURE:
if F_GET_BUYER_NAME(L_error_message,
:B_buyer_info.TI_buyer_no,
:B_buyer_info.TI_buyer_name) = FALSE then
emessage(L_error_message);
raise FORM_TRIGGER_FAILURE;
end if;

By raising FORM_TRIGGER_FAILURE, you jump out of the Execution section of your PL/SQL block
and go to the when FORM_TRIGGER_FAILURE portion of the Exception section:
EXCEPTION
when FORM_TRIGGER_FAILURE then
raise;
when OTHERS then
emessage (SQLERRM);
raise FORM_TRIGGER_FAILURE;

When you explicitly raise a FORM_TRIGGER_FAILURE exception, Forms halts the processing in the
trigger, and moves to the Exception section. In the Exception section, you then perform a
command called raise. RAISE looks to see if this trigger was fired as the result of some other
process. If it was, RAISE halts the calling process in a reverse-domino effect*:

© 2006 Enabler Page 57


Development Standards

TRIGGER
PROCEDURE
FUNCTION, etc

3. RAISE in the Exception 2. Processing is stopped


TRIGGER
section looks for a calling in this trigger.
program and, if found,
will stop the calling
program’s processing.
1. Error occurs here, raising
FORM_TRIGGER_FAILURE.

For unexpected errors (your WHEN OTHERS section), you print the error message generated by
the SQL server (SQLERRM) and then raise FORM_TRIGGER_FAILURE. This, in turn, stops the
processing, and stops calling processes.
* Technically, the line raise kicks the processing back out to the calling process Exception section
and executes the calling process Exception section. In the illustration below A, B, and C all have
Oracle Retail’s standard Forms Exception section code:
EXCEPTION
when FORM_TRIGGER_FAILURE then
raise;
when OTHERS then
emessage (SQLERRM);
raise FORM_TRIGGER_FAILURE;

6. Process A’s exception section


1. Process A calls process B A
runs and processing halts

5. Process B’s exception section


2. Process B calls process C B
runs (and RAISEs)

3. Process C encounters an error and 4. Process C’s exception section


C
raises FORM_TRIGGER_FAILURE runs (and RAISEs)

© 2006 Enabler Page 58


Development Standards

7.23 GUI Standards


7.23.1 Boilerplate Text
Due to Internationalization, Boilerplate Text is no used in Oracle Retail. All instances in which
Boilerplate Text is needed requires that Display Items be created instead.
To create Display Items which will act as Boilerplate Text in your Form follow these steps:
• Create a new Display Item (as opposed to a Text Item) in whichever block makes the most
logical sense. This is likely to be the block which contains the item to which your Display Item
will refer;
• Change the Display Item’s name to be DI_<name> where <name> represents what the
boilerplate text specifies (e.g. DI_store_name) ;
• Set the following properties on the Display Item:
• Maximum Length: set to be at least double the length that is required by your Initial Value so
that the item will not be too small should translated text be a greater number of characters;
• Initial Value: set to be the text you wish to have displayed;
• Database Item = NO;
• Bevel = NONE;
• Visual Attribute Group: either BOILERPLATE_TEXT or BOLD_TEXT depending on how you
wish the text to be displayed;
• Height and Width: set to fit the text of the field. Wherever space allows, make the field
longer than is actually needed to better accommodate translated values.

7.23.2 Canvases
Canvases should be organized in a logical manner, grouping together related items in “sections".
When placing items on a canvas the goal is to conserve space. Group items tightly together and if
additional space exists on the canvas shrink the canvas size.
Attributes:
Bevel : None
Visual Attribute: VA_CANVAS

7.23.3 Visual Attributes


The following visual attributes should be assigned to corresponding items in a form. These visual
attributes should be referenced from the reference form FM_refer.
VA_CANVAS,
Camel foreground and background. Used for non-editor canvases.
CANVAS
Black text on camel background. Used for the actual checkbox and allows
VA_CHECK_BOX,
the user to add a label to the right with a VA_DISPLAY_ITEM visual
CHECKBOX_ON
attribute.
VA_DISPLAY_ITEM,
Black text on a drab background. Used for Labels and non editable fields.
TEXT_ITEM_OFF

© 2006 Enabler Page 59


Development Standards

VA_EDIT_ITEM,
Black text on white background. Used for Enterable/Editable fields.
TEXT_ITEM_ON
VA_EDITOR,
Black text on white background. For the Editor canvases.
EDITOR
VA_HIGH,
Black text on khaki background. Used for "selected" fields and records.
HIGHLIGHT_ON
VA_LOV, LOV Black text on White background. Used for List of Values.
VA_WINDOW,
Camel foreground and background. Used for windows.
WINDOW

7.23.4 Windows
A window is the basic interface that is used to conduct a dialog with the user. Multiple windows
can be presented at the same time. This allows for greater flexibility in the system. Modal
windows force the user to perform a series of tasks in a certain order, but it is strongly
recommended that a modal window be avoided if at all possible (the only valid exception to this
are pop-up windows). A modeless window, on the other hand, allows the user a degree of
freedom in jumping between windows and/or other applications like a word processing or
spreadsheet application.
Neither vertical nor horizontal scroll bars should be used at the window level.
A window may contain two levels of push buttons -- one which affects the entire window and one
which affects a subset of controls.
The title bar is the top bar on every window. It should include the title of the window. The title
should capitalize the first letter of every word and the text should be centered. Window titles
should be in the following format:
Window Description (filename)

The filename should be separated from the Window Description by 10 spaces.

7.23.5 Blocks
Multi-Record Blocks
To achieve the new look, perform the following steps:
• Set all text fields in the block to Bevel = NONE
• All items in the multi-record block should have distance between records set to 1 (item-level
property)
• All items in the multi-record block should be set 1 pixel apart horizontally. This is done most
easily by opening the appropriate canvas and arranging the items using the left and right arrow
keys. (This is not required for multiview blocks) Checkboxes should be 6 pixels apart from other
items.
• Create a rectangle that encompasses the multi-record block. The bevel should be set to raised.
The rectangle should appear fully but should not extend far beyond the fields (just to be seen).
Set the rectangle’s visual attribute to RECTANGLE.

© 2006 Enabler Page 60


Development Standards
• For multiview blocks, the rectangle needs to be as wide as the multiview can possibly be. (Use
the expand option to test this)
If you have a parent/child relationship in the same form, the parent block is raised as just
described. The child block has a similar rectangle, but the rectangle has a bevel set to inset.

7.23.6 Input/Output
Check Boxes
Attributes:
Height : 10
Distance Between Records : 6 (multi-record blocks only)
Visual Attribute : VA_CHECK_BOX
Prompt Attachment Offset : 5 (multi-record blocks only)
A check box is used to display a setting choice. This control is a simple square with two, clearly
distinguishable states, checked and not checked. Checkboxes are used for values that have two
distinguishable values, yes and no, or checked and unchecked.
In most cases, Check Boxes should be grouped together in a Group Box with a title.
For single record blocks, use the Label field instead of a Prompt and adjust the checkbox’s width to
accommodate the text. Have the Label appear to the right of the checkbox.
For multi-record blocks, do not use the Label field – use the Prompt instead. The Prompt
Attachment Offset should be set to 5, and the Width of the checkbox should be 10.
Text Item
Attributes:
Height : 15
Visual Attribute : VA_EDIT_ITEM,VA_DISPLAY_ITEM,TEXT_ITEM_ON,TEXT_ITEM_OFF
Text items are used to display enterable or non-enterable dynamic fields, usually containing
information queried from the database.
Character Text Items are left justified.
Numeric Text Items are right justified. Amount and quantity fields must be left justified (and are
defined as such in their corresponding Property Classes) because fields that are right justified do
not allow scrolling through the field if the value is larger than the field displays.
Display Item
Attributes:
Height : 15
Bevel : None
Visual Attribute: BOILERPLATE_TEXT
With the usage of prompts, these should be virtually non-existent. One place they may still need
to be used is when a dynamic value will have multiple lines. Prompts do not support multiple lines
when setting a prompt dynamically.
Drop Down List Box (POPLIST)
Attributes:
Height : 15
Required : TRUE or FALSE

© 2006 Enabler Page 61


Development Standards
Visual Attribute: VA_EDIT_ITEM, TEXT_ITEM_ON
Default Value : None
The poplist is hidden until the user clicks the down arrow. The poplist contains a list of data or
settings that a user can scroll through and select.
The list of choices should be organized in alphabetical order, or some functional order, to allow
easy selection by the user.
The list box should be wide enough to display the longest choice after it has been selected.
The list box may cover other controls in the window when it is dropped down. The designer must
take care that controls that the user may reference to make a choice are not covered by the list
box when it is open.
A list box should contain at least three choices. If only two choices are available, a radio button
group or a single check box would be more applicable. This standard will vary, however,
depending upon the window layout and whether or not there is room for such items.
The default value should be first in the list. The default choice should be NULL in the property
sheet.
If the list box is required, and there is a default value, then the REQUIRED property can be set to
true to avoid a NULL value being displayed in the list box.
Group Boxes
A group box is a rectangle drawn around a group of fields to indicate that the fields are related. It
is not functional. The group box’s bevel should be inset.
If a title is required for the group, then it should overlay the box in the upper left-hand corner and
have MS Sans Serif font size of 8, bold. Use the first letter capitalization style.
Isolate and name only groups of related controls that work together. Typical uses of groups are
radio buttons, check boxes, and entry fields.
Iconic Push Button
Iconic buttons are used for LOVs, comments, calendars, etc.
Attributes:
Width : 14
Height : 15
Icon Name : various
Visual Attribute : TEXT_BUTTON_VAL_OFF
Current Record Visual Attribute: TEXT_BUTTON_VAL_OFF
Iconic pushbuttons should have the Mouse Navigate property set to NO on single record blocks,
and set to Mouse Navigate YES on multi-record blocks. Using
RWIDGET.TURN_ON_SINGLE_RECORD_LOV and RWIDGET.TURN_ON_MULTI_RECORD_LOV will
set these properties correctly.
In addition to setting the Visual Attribute, set the Current Record Visual Attribute to be
TEXT_BUTTON_VAL_OFF. This will prevent the buttons from appearing blue on the web.
Prompt
Prompts are used to label items instead of boilerplate text. Prompts are used for both single and
multi-record blocks. Once you attach a prompt, you should never align the text, just the items. A
prompt is displayed automatically with its item, so no turning on and off of the prompt is required.

© 2006 Enabler Page 62


Development Standards
Display items will seldom be required now that prompts can be set dynamically using
Set_Item_Property (codes must still be used however to populate the value).
Prompts are not used for checkboxes in single record blocks – Labels are used instead. For multi-
record checkboxes use the Prompt, setting the Prompt Attachment Offset to 5.
To add a prompt set the following properties:
• Set the prompt to the word(s) used to describe the item (be careful not to use extra spaces)
• Prompt Display should be left as First Record
• Prompt Justification should be right for single-record blocks, centered for multi-record blocks
• Prompt Attachment Edge can be left at Start for single record blocks, Top for multi-record
blocks.
• The prompt alignment should be changed to center.
• The prompt attachment offset should be set to 5 for single-record, 3 for multi-record blocks.
• The prompt alignment offset should remain 0.
• The prompt reading order should remain default.
• The prompt visual attribute group should be PROMPT_TEXT.
Radio Groups
Attributes:
Visual Attribute: VA_DISPLAY_ITEM
A radio button has two distinct states, on or off. Only one radio button within a group can be
selected at one time. A radio button is always used when choices are mutually exclusive (you must
chose only one from the group.)
A default option button must always be pre-selected for the user and should be the first button on
the left or the top button when arranged vertically.
Always use the label for the radio items so that tabbing looks normal – labels should be to the
right of each radio button.
In most cases the Radio group should be surrounded by a Group Box.
Scrollbars
Attributes:
Visual Attribute: SCROLLBAR
Width : 11
Scrollbars are placed on the left-hand side of a multi-record block. This location is used to match
Multiview Forms which have dynamically changing right margins.

7.23.7 Buttons
Non-Iconic Push Buttons
Attributes:
Width :60
Height :16
Visual Attribute :TEXT_BUTTON_VAL_OFF

© 2006 Enabler Page 63


Development Standards
Height will never vary, however width can be increased for text to be fully displayed. If it is
increased, all other buttons on the screen should be expanded to be the same width.
All buttons should be associated with a separate block called "B_action" unless this causes
unnecessary programming hardship.
The text on a button should always fit neatly in the button. Button text should follow first letter
capitalization style except for OK, SKU, UPC, and VPN.
Command buttons that are not currently available should be disabled and, in some cases, hidden.
Try to avoid having buttons enabled when they can never be utilized by the user. For example, if a
form is accessed in View mode, users can never use the Delete button so it should be disabled.
However, if the user must do something to the Form for a button to be applicable, then it should
always be enabled but should display a message when clicked telling the user what to do. For
example, the OK button is always enabled, even though the user may have to enter required fields
before it is applicable. Clicking on the OK button will display a message if required information is
missing.
Do not use "Close" on a button. This word is reserved for the control menu bar and the Search
forms where Cancel is never applicable (i.e. itemfind, ordfind, pcfind, hierorg, hiermrch, etc.)
Access Keys
All command buttons should have Access Keys defined for them. Access Keys are single underlined
characters within the button text that allow the user to invoke the action of choice by pressing the
ALT + the underlined action letter. For example, to delete a record, the user can either click on the
Delete command button or press ALT + D. Access Keys are case-sensitive! This means that if you
wish to use the letter "d" for Add, the Access Key must be in lower case on the Property Palette.
Also, do not use duplicate access keys within a window. Note that access keys on the menu
override those at the window level.
Mouse Navigate
Any button that cancels, clears, or deletes the current record must have the Mouse Navigate
property set to NO on its property sheet to prevent validation triggers from needlessly firing. This
will include cancel buttons, delete buttons, refresh buttons, etc.
Command Button Order
These command buttons MUST be in the following order, if they exist on the form:
• OK on the far left
• OK + Repeat to the right of OK
• Cancel on the far right
• Delete to the left of Cancel
• New, Edit, View - in that order, to the right of OK
Below is a list of the standard action text buttons:
Button Description
OK Commits and closes the form.
OK +
Commits the Form and leaves the associated window open.
Repeat

© 2006 Enabler Page 64


Development Standards

Cancel Closes the associated window and nullifies any user changes to the window.
Adds a new record in the multi-record block or calls an additional form to add
Add
another.
Delete Removes existing data from selected field, record, block or current form.
Next Accepts the current data on the window and transfers control to the next window.
Accepts the current data on the window and transfers control to the previous window
Prev
in the flow.
Print Prints a copy of the specified information.
Refresh Clears out fields and sets them back to the default values.
Search Opens a criteria window to allow the user to search for more data.
Buttons are placed on the bottom of the canvas and right-aligned. They should be placed 5 pixels
apart.

7.24 List of Values (LOV) Buttons


Forms List of Values are pop-up windows which display the dynamic results of a query. To build a
List of Values, two separate Forms elements must be created: an LOV and a Record Group. Use
Forms online help for details on creating LOVs and Record Groups. We also must create an iconic
button with an icon name of ‘listval’.
At Oracle Retail, the user can access a List of Values by two different methods. The first method is
by pressing the LOV button. The second method is by pressing the F9 key when the cursor is in the
field to be populated by the List of Values. For both methods to work, the following code must be
used:
Put a WHEN-BUTTON-PRESSED trigger on the LOV button and add the following code:
BEGIN
-- goes to text item LOV is firing for (replace block.item with your
item)
Go_Item ('block.item');
-- the second line must be written EXACTLY as it appears here
Do_Key('List_Values');
END;

Put a KEY-LISTVAL trigger on the item which will be populated by the list and add the following
code:
BEGIN
-- replace lov_name with the name of your LOV
if F_SHOW_LOV('LOV_name') then
Do_Key('Next_Item');
end if;
END;

If the user is on the field and presses F9, the trigger KEY-LISTVAL is fired. That’s because Oracle
Retail has mapped the F9 key to that particular trigger.
If the user presses the button, we navigate to the field and then perform a Do_Key ('List_Values')
which fires the KEY-LISTVAL trigger.
A note on F_SHOW_LOV:

© 2006 Enabler Page 65


Development Standards
F_SHOW_LOV differs from the Forms Built-In Show_Lov. Should the list of values query return no
rows, F_SHOW_LOV displays a message indicating the query returned nothing. Forms Show_Lov
pops-up an LOV, but the LOV has nothing in it. Oracle Retail prefers to specifically indicate to the
user that there are no available values rather than have an empty box appear.

7.25 Menus
7.25.1 General Menu Standards
• The most common choices in a pull down menu should be near the top.
• If a menu item is not available it should be disabled, not hidden.
• Menu choices should be single words when possible.

7.25.2 Access Keys for Menu Items


A unique letter access key should be underlined and enabled for each menu bar option and pull-
down menu choice. There are special rules for defining access keys in Menus (as opposed to in
Forms). On the property palette for menu items are entries for the Label and for the Keyboard
Accelerator. Keyboard Accelerators are very different from Access Keys and should not be used
(see the note below for a detailed explanation). Instead, for Menu Items put an ampersand (&) in
the Label immediately before the letter you wish to use as an access key:

Using the ampersand (&) in the Label works the same as defining an Access Key. In the example
above, the letter “y” will be displayed as underlined when the menu is run, and typing Alt + y at
run-time will choose that menu item.
If you do not specify the Access Key by using the ampersand method the menu will, by default,
underline the first letter in the first word of the Label for you. This can be a problem since it isn’t
intelligent-enough to ensure that the first letter isn’t already in use by another menu item. Use the
ampersand method even if the Access Key is to be the first letter in the first word.
© 2006 Enabler Page 66
Development Standards
NOTE ON KEYBOARD ACCELERATORS: An example of a Keyboard Accelerator is the combination
Ctrl + X for “Cut”. Keyboard Accelerators have to be created using Oracle Terminal, then mapped
to an item on its property palette.

7.26 Modularized Code


A common mistake made by new developers of Oracle Retail code is to create a Program Unit for
every PL/SQL block, and call those Program Units from triggers, when logically the code itself
belongs in the trigger. Unfortunately, there are no hard and fast rules. However, small and simple
PL/SQL blocks, especially those which do not perform validation or make calls to the database,
most likely belong in the triggers themselves. Complex PL/SQL blocks, calls to database functions,
and code that is repeated should be extracted into Program Units.
One thing you may consider doing to modularize code is manually firing existing triggers from
within other PL/SQL blocks. Take, for example, starting a multi-record block that needs to have
certain fields enabled and disabled in the multi-record block. The enabling/disabling will be coded
in the WHEN-NEW-RECORD-INSTANCE trigger. This code can be reused in P_FORM_STARTUP by
doing the following:
Go_Block('B_detail');
Execute_Trigger('WHEN-NEW-RECORD-INSTANCE');

Anytime you can execute an existing trigger or use a Do_Key command, it should definitely be
done.

7.27 Mouse Navigate


Mouse Navigate is a property for buttons in Oracle Forms. It is critical that the Mouse Navigate
property be understood and set correctly for your Form to work properly.
When you click on a button with your mouse, navigation is performed in your Form. That is,
clicking on a button moves the focus in the Form from whatever item you were last on to the
button you just clicked. By virtue of your having navigated to the button, various validation
triggers may fire.
For example, if you just typed ‘1234’ into a store number field and then clicked on a button, the
WHEN-VALIDATE-ITEM trigger on the store number field will fire. Most of the time this is a good
thing –we want to validate that ‘1234’ is a store number. Under certain circumstances, however,
this is not desirable.
Imagine that you are creating a new purchase order in the system. Midway through the process of
creating the order you realize another identical order was already in the system. Your work is
redundant, so you click on the Delete button. Clicking on the Delete button navigates to the
block.item 'B_action.PB_delete'. By virtue of having navigated, the WHEN-VALIDATE-ITEM trigger
on the order number fires and tells you your order number is a duplicate of one already in the
system. “I don’t care if it’s a duplicate!” you say, “I’m trying to get rid of it!”
Imagine instead that your order number is unique but the order is not yet complete. If you click on
the Cancel button, navigation is again performed and the order block’s WHEN-VALIDATE-RECORD
trigger fires and presents you with an error message stating that you have left a required field
blank.

© 2006 Enabler Page 67


Development Standards
What the property Mouse Navigate represents is whether clicking on the button with the mouse
initiates navigation in the Form. When Mouse Navigate is set to YES, clicking on a button with a
mouse performs the navigation as usual. By setting the Mouse Navigate property to NO on a
button, clicking on the button will fire the button’s code but will not navigate to the button.
Because no navigation occurs, no validation triggers will fire. In the case of Cancel and Delete
buttons illustrated above, setting Mouse Navigate to NO would cure our problems and let us
cancel or delete incomplete or invalidated data.
Usage Tips:
Any button that cancels, clears, or deletes the current record must have the Mouse Navigate
property set to NO on the property palette to prevent validation triggers from needlessly firing.
This includes cancel buttons, delete buttons, refresh buttons, etc.
Iconic pushbuttons should have the Mouse Navigate property set to NO on single record blocks,
and set to YES on multi-record blocks. Using RWIDGET.TURN_ON_SINGLE_RECORD_LOV and
RWIDGET.TURN_ON_MULTI_RECORD_LOV will set these properties correctly. Use those variants
of RWIDGET for any iconic button in a multi-record block.

7.28 Multiple Language Support


Overview
Multiple language support assumes that there will be a primary language preferred by a majority
of users and that there may be users who prefer a different (secondary) language. A user
preference determines the language in which the GUI interface and translatable text data will
appear for any given user. The Primary language is stored on the SYSTEM_OPTIONS table and each
user’s preferred language is stored on the USER_ATTRIB table.
Most of the key text fields in the system are enabled to present data to secondary language users
in their preferred language. Text data on the tables, however, is generally stored in the primary
language, which implies that all data entry must be performed in the primary language. For
presentation in a secondary language, the primary language description is replaced (when a
translation exists), by translated values fetched from a central dictionary. Translations must be
entered into this “dictionary” for data to appear in any secondary languages.
The table TL_SHADOW acts as the dictionary. The table uses the primary language text value (in all
capital letters) which, with the (secondary) language number column, form the primary key. The
remaining column is the translated value:
lang NUMBER(6)
key VARCHAR2(250)
translated_value VARCHAR2(250)

A single installation of the application handles all supported languages. The language used on the
windows is varied at the individual level. For instance, a retailer could choose German as the
default language, but any individual user who prefers to use English, French, Japanese or any other
supported language can do so easily by setting his or her individual user language preferences.
Technical Implementation
These sections outline the technical implementation procedures for installing multiple language
support.

© 2006 Enabler Page 68


Development Standards

7.28.1 List Items


Use the function P_POPULATE_LIST in P_FORM_STARTUP to populate any list items.
P_POPULATE_LIST will ensure that secondary language users see translated values. For more
details, see the P_POPULATE_LIST section.

7.28.2 Code Description and Dynamically Populated Labels


Tables exist to decode the various codes throughout the system. The values on these tables should
also be used to populate drop-down lists and dynamic labels. The tables are CODE_HEAD and
CODE_DETAIL and there is a new form for adding data into them.
CODE_HEAD contains the code description and the code type, a four-letter code that distinguishes
this code type from others. The code type is used as a foreign key on the CODE_DETAIL table,
which also includes the code, its decode (description), a system required indicator (always Y for
codes added during development), and a sequence number that is always required even though it
may only be used to determine list box order.
Code descriptions and labels are retrieved using the LANGUAGE_SQL.GET_CODE_DESC function.
Labels are simply made-up codes.
New Codes
Most codes needed in the system already exist, even many of the necessary labels. Search the
table for a partial description matching one of the elements of a code you think you need to create
before adding a new code type. Avoid editing existing codes without doing a complete impact
analysis because many codes are used in more than one place and you would not want to
unknowingly add an invalid value to a list item.
SPECIAL NOTE
Although you can use the online form to add codes to the system or edit existing codes, it is
essential that a database request be made to update the script that populates the Codes table or
your changes will not make it to the production version!

7.28.3 Record Groups


Record groups are coded as usual and then require a modification to enable them to select the
Translated Description field for a row if that row has a translation.
There are several straightforward steps you use to create a translation enabled record group. This
is only necessary for record groups that select translation enabled descriptions.
1. Duplicate the query and separate the two queries with union all. Only retain one Order by and
one Group by clause (if they exist) and move them to the very bottom. Column numbers
instead of names may be required.
2. Add an additional where constraint GET_PRIMARY_LANG = GET_USER_LANG to the first query
and an additional where constraint GET_PRIMARY_LANG != GET_USER_LANG to the second
query. All other changes are made to the second query alone.
3. Wrap NVL(tl_shadow.translated_value, [description]) around the [description] throughout the
second query and add the table tl_shadow in the from component.
4. Add the additional where clause conditions (outer join) to the second half of the query
UPPER([description]) = tl_shadow.key (+) and GET_USER_LANG = tl_shadow.lang (+).

© 2006 Enabler Page 69


Development Standards
5. Repeat steps 3 and 4 for multiple descriptions, with separate references to the tl_shadow table
in the From clause aliased as difference tables.
Examples of original and new queries:
Simple:
Original:
select d.dept_name,
d.dept
from deps d
order by d.dept_name

New:
select d.dept_name dept_name,
d.dept
from deps d
where GET_PRIMARY_LANG = GET_USER_LANG
union all
select NVL(tl.translated_value, d.dept_name) dept_name,
d.dept
from deps d,
tl_shadow tl
where GET_PRIMARY_LANG != GET_USER_LANG
and GET_USER_LANG = tl.lang (+)
and UPPER(d.dept_name) = tl.key (+)
order by dept_name

Value entered in a description field limits returned values:


Original:
select d.dept_name,
d.dept
from deps d
where d.dept_name LIKE '%'||:block.item||'%'
order by d.dept_name

New:
select d.dept_name dept_name,
d.dept
from deps d
where GET_PRIMARY_LANG = GET_USER_LANG
and d.dept_name LIKE '%'||:block.item||'%'
union all
select NVL(tl.translated_value, d.dept_name) dept_name,
d.dept
from deps d,
tl_shadow tl
where GET_PRIMARY_LANG != GET_USER_LANG
and UPPER(d.dept_name) = tl.key (+)
and GET_USER_LANG = tl.lang (+)
and NVL(tl.translated_value, d.dept_name) LIKE '%'||:block.item||'%'
order by dept_name

7.28.4 Labels
All the labels shown on the screen are defined in database. There are two tables where the labels
and its translation are defined: FORM_ELEMENT and FORM_ELEMENT_LANG.

© 2006 Enabler Page 70


Development Standards
FORM_ELEMENT:
It holds the master list of items for all forms whose labels/prompts are translated. These items
include LOV, Push Button, Text Item, Window, etc. This information will always be in English.
FORM_ELEMENT_LANG:
It holds translated values for the labels/prompts on forms. This information will be in a language
that is defined on the LANG table. Even English language users see data from this table, as the
client may customize the text of a given field.
Every time a form is created or changed, you must create a script to insert data into these two
tables for all labels/prompts created or modified.
Note: FORM_ELEMENT and FORM_ELEMENT_LANG tables are used for translation only in ORMS
version 12 or higher.

7.29 Naming Standards


7.29.1 General Naming Standards
The xx portion is all lowercase.
Alert ALT_xx
B_xx (Base table blocks are named after the table i.e.
Block
B_ordhead)
Canvas C_xx
Chart CH_xx
Check Box CB_xx
Cursor C_xx.
Display Item DI_xx
Form Module FM_xx
Function (local to form) F_xx
Image IM_xx
List Item LI_xx
LOV_(field name) (If LOV is used on > 1 block, use block and
List of Values
field name)
Local Variable L_xx
Menu Module MM_xx
Mirrored Item* MI_xx
Input parameter to a
I_xx
procedure/function
Output parameter to a
O_xx
procedure/function
In/Out parameter to a
IO_xx
procedure/function

© 2006 Enabler Page 71


Development Standards

Parameter PM_xx
The name of the calling program with no prefix. For example,
Parameter List any Parameter Lists created in ordadd.fmb would be named
'ordadd' regardless of what Form they're calling.
Procedures P_xx
Push Button PB_xx
Radio Group RG_xx
Record Groups REC_(block where REC is used)_(item where REC is used)
Sending parameter to a
S_xx (e.g. 'cursor C_GET_DEPT(S_dept) is')
cursor
TI_xx (Base table items must be named after the associated
Text Item*
table.column, omitting the TI_prefix.)
User Area UA_xx
Visual Attributes VA_xx
Window W_xx
* Mirrored items are MI_xx when they are Text items, otherwise they should be MI_(item
type)_xx. For example, a mirrored push button would be called MI_PB_ok.

7.29.2 Other Naming Standards


If there is only 1 window and 1 canvas for the form, then the .fmb/.fmx file name, canvas name,
form/module name and window name should all match except for the prefix.
The maximum length of a form name is 8 characters plus the .fmb extension.
The first 3 to 5 characters of the form name should identify what dialog it belongs to. For example,
all forms in the Orders dialog should begin with "ORD" and all forms in the Layaway dialog should
begin with "LAY."
Do not use underscores in the form names. i.e. use ordadd.fmb and W_ordadd, not ord_add.fmb
and W_ord_add.
Be as descriptive as possible. Try to avoid naming forms with numbers.
Find Forms: W_xxFIND where xx is the dialog abbreviation.

7.30 Navigation
Oracle Retail products always give the user the option of using either the keyboard or the mouse
to perform any function. The MS Windows standard of providing short cut keys for push buttons
and menu items will be followed. Users should be able to tab from enterable field to enterable
field and expect to go from left to right, top to bottom.
Things to remember:
• Tab order should only include navigable blocks.
• Tab order in a multi-record block should go from left to right through the enterable fields of a
single record. Upon reaching the last item of a single record, tabbing should proceed to the

© 2006 Enabler Page 72


Development Standards
next navigable block rather than the next record in the multi-record block. Within a multi-
record block, the up and down arrow keys are used to move between records.
• Back tabbing (Shift + Tab) should perform the reverse of forward tabbing.
When coding Forms, keep the use of Key-Next and Key-Prev triggers to an absolute minimum
because they make maintenance more difficult. One can usually get by without using any Key-Next
or Key-Prev triggers when the items are placed in the correct order in the Object Navigator and
the block properties are set to go to the next or previous block after the last item on that block.
To use no navigation triggers perform the following steps:
1. Assign Next and Previous Blocks on the blocks’ property sheets, with the Navigation Style
property set to Change Data Block.
2. Place the items within blocks in the Object Navigator in the order they appear logically on the
Form (i.e. don’t put a button that appears in the header in the action block)
3. Do not specify next navigation and previous navigation items at the item level
4. Always have one navigable item in each block (remember secondary languages!)
NOTE: If Key-Next or Key-Prev triggers are used, then when checking items to see where to tab
next, always check the UPDATEABLE or ENABLED property, not the visual attribute name.

7.31 Opening Another Form


When launching another Form, always use Open_Form, never Call_Form. An example of how
to launch another Form with parameters follows:
DECLARE
PL_id PARAMLIST;
BEGIN
P_DESTROY_PARAMETER_LIST('nameofTHISform’');
PL_id := Create_Parameter_List('nameofTHISform');
Add_Parameter(PL_id,
'PM_mode'
TEXT_PARAMETER,
'NEW');
Add_Parameter(PL_id,
'PM_dept'
TEXT_PARAMETER,
:B_head.dept_no);
. . .
Open_Form('nameoftheformTOBEOPENED'
ACTIVATE,
SESSION,
PL_id);
EXCEPTION
when FORM_TRIGGER_FAILURE then
raise;
when OTHERS then
emessage(SQLERRM);
raise FORM_TRIGGER_FAILURE;
END;

© 2006 Enabler Page 73


Development Standards
When using Open_Form, employ the ACTIVATE and SESSION parameters unless the detail design
instructs otherwise. ACTIVATE sets the focus to the form being opened to make it the active form
in the application. SESSION specifies that a new, separate database session should be created for
the opened form. This means POST and COMMIT operations in one form will only cause posting,
validation, and commit processing to occur for that particular form.
* Call_Form has memory restrictions and other behaviors which are undesirable for a complex,
multi-form application. For example, Call_Form will launch another form and give that form the
previous form’s menu instead of letting it use its own menu.
Forms Online Help declares: “Use Open_Form to create multiple-form applications, that is,
applications that open more than one form at the same time.”

7.32 POSTing Form


Posting in a Form is required in only a few circumstances and should only be performed in these
instances:
• If a record is deleted in a multi-record block
• If I have 2 windows within my Form (parent/child type relationship) posting is required in the
header before opening the detail
• If I display two multi-record blocks and the second is queried based on the first, the first block
will need a POST in the WHEN-NEW-RECORD-INSTANCE trigger in order for the second block to
be queried properly
• If I call a function that needs to read data from the Form (like a recalculation function), a POST
will be necessary before calling that function
• If I need to perform a function that will execute a query (like filter).
Remember, anytime a POST is performed, it must be followed with a call to
WINDOW_HANDLER.SET_FORM_CHANGED. See the section on WINDOW_HANDLER for details.
Important usage notes:
Both POSTing and COMMITting in Forms can result in unusual error-handling behavior. If a trigger
that is implicitly fired by a POST or COMMIT raises an exception, the processing does not halt as
expected.
For example, take the following Forms code:
POST;
WINDOW_HANDLER.SET_FORM_CHANGED;
Go_Block('B_head');

Depending on what the user has done, the POST might cause triggers (such as PRE-INSERT, PRE-
DELETE, POST-UPDATE, etc.) to fire. If an exception is raised in an implicitly-fired trigger, the
trigger will halt but the lines of code following the POST will still be run! That means that even
though something went wrong while POSTing, the Form will still try to Go_Block('B_head') – not
what we would want to happen.
In situations such as this, it becomes necessary to check after the POST or COMMIT if everything
went as planned. We can use the :system.form_status to determine how the POST or COMMIT
went (see the information on :system.form_status in the section on :system Variables for details)
and either continue or halt as needed:

© 2006 Enabler Page 74


Development Standards
POST;
if :system.form_status != 'QUERY' then
the POST failed so we must deal with the error…
else
WINDOW_HANDLER.SET_FORM_CHANGED;
end if;

In P_EXIT.SAVE_FORM (the standard Form-exiting code in our template) you can see the following
example:
Commit_Form;
---
if :system.form_status != 'QUERY' then
emessage('COMMIT_FAILURE');
raise FORM_TRIGGER_FAILURE;
end if;

7.33 P_POPULATE_LIST
P_POPULATE_LIST is an Oracle Retail Function used to populate list items dynamically. List Items
can be populated manually but, since our products are used in many languages, we don’t want to
hard-code any values.
P_POPULATE_LIST draws information from the table CODE_DETAIL based on the CODE_TYPE fed
into it. It takes the values that correspond to the CODE_TYPE from CODE_DETAIL and puts them in
the list item you specify. This is the structure of the CODE_DETAIL table:
CODE_TYPE VARCHAR2(4)
CODE VARCHAR2(6)
CODE_DESC VARCHAR2(40)
REQUIRED_IND VARCHAR2(1)
CODE_SEQ NUMBER(4)

For any given CODE_TYPE used by P_POPULATE_LIST, the CODE_DESCs are what is displayed
to the user and the CODEs are the actual values that the list holds. These values (the information
from the CODE column) are what will be written to or read from the table.
An example of giving a list items values in P_FORM_STARTUP might look as follows:
P_POPULATE_LIST('B_lc_comp.comp_type', 'CCTP');

Here, the name of the list item is B_lc_comp.comp_type, and the information that will be put into
the list corresponds to the entries on the CODE_DETAIL table where the CODE_TYPE = 'CCTP'.
P_POPULATE_LIST executes the following query, putting the values into the list item:
select code,
code_desc
from code_detail
where code_type = 'CCTP'
order by code_seq;

P_POPULATE_LIST can also be used to give a list item a default value by using the following syntax:
P_POPULATE_LIST(block.list_item_name, code_type, default_value)

There is another function P_POPULATE_LIST_NO_CLEAR with the same parameters. If default


values are used with multiple lists, it may be necessary to use the NO_CLEAR function to avoid
being prompted to "save changes" before the Execute_Query fires. It may also be necessary to
populate default values manually elsewhere in the code, depending on functional requirements.

© 2006 Enabler Page 75


Development Standards

7.34 Reusability & Maintenance


Whenever possible steps should be taken to increase the reusability of code and decrease future
maintenance.
• Frequently used blocks of code should be identified and placed in a package on the database.
Also code that makes frequent database hits and/or heavy calculations should be placed in a
package to increase performance of the forms.
• Use reference forms for visual attributes, toolbars, multi-view, calendar, etc. All new forms
should start with the FM_TEMPL form which has many of these standards already written into
it.
• Identify variables and flags that are used across different triggers in a form. These values should
be stored in the INTERNAL_VARIABLES package spec (no body is required for this package)
instead of in a form parameter. Parameters should only hold values that are to be passed to
other forms.
When a new INTERNAL_VARIABLES is created or modified it should be well documented, with
comments, where its value is set and referenced. Below is an example of an INTERNAL_VARIABLES
package spec:
PACKAGE INTERNAL_VARIABLES IS
-- The new supplier flag is set in P_FORM_STARTUP and referenced
-- in FROM_VALIDATION.SUPPLIER after which it is set back to 'N'
-- if it is not already set to 'N'.
GV_new_supplier_flag VARCHAR2(1) := 'N';
END;

7.35 RWIDGET
RWIDGET is an Oracle Retail library package that sets an item’s properties, enabling and disabling
the item and causing the item to appear either enabled or disabled. A developer could issue a
series of Set_Item_Property commands instead of using RWIDGET, but RWIDGET saves effort and
applies a consistent look and feel. Because of these benefits, RWIDGET is required when enabling or
disabling fields. Never use Set_Item_Property to set the visual attribute, enabled, updateable,
navigable, etc.
When you first set up an item, set its initial attributes on the property sheet (e.g. if a field is display
only, its property sheet should be set to VA_DISPLAY_ITEM for the visual attribute, Update
Allowed = FALSE, Insert Allowed = FALSE, and Navigable = FALSE). Then, use RWIDGET any time it
is necessary to change the item’s attributes at run-time, such as turning the item on when it is off.
The different functions within the RWIDGET package are listed in the table below.
RWIDGET Function Use
TURN_ON Enables a field or button.
TURN_OFF Disables a field or button.
Enables the Cancel button, but leaves it as Mouse Navigate
TURN_ON_CANCEL_BUTTON NO, so that no validation is performed if users click the
button.
DISPLAY_ON Turns display on.

© 2006 Enabler Page 76


Development Standards

DISPLAY_OFF Turns display off.


CHANGE_MASK Changes the mask.
Disables a field or button, but leaves navigable YES, so that
TURN_OFF_VIEW_MODE
the field can be navigated to.
Turns on a LOV button for a single record block. Sets Mouse
TURN_ON_SINGLE_RECORD_LOV
Navigate to NO.
Turns on a LOV button for a multiple record block. Sets
TURN_ON_MULTI_RECORD_LOV
Mouse Navigate to YES.
Checks to see if the Form should be translated. If so, the
TL_TURN_ON translated field is set to display on (covered in greater detail
in the section on multiple language support).

7.35.1 Usage Hints


It is sometimes helpful to have one or more program units in your Form devoted to enabling or
disabling groups of items on the Form, such as:
• P_VIEW_MODE
• P_ENABLE_HEADER
• P_DISABLE_DETAIL
• P_ENABLE_ACTION
Call these program units from the places within your code, such as P_FORM_STARTUP, after
querying items for the detail block, and so on. This isolates the enabling/disabling of the items into
a central location and eases the maintenance of the code.

7.35.2 Note on RWIDGET.DISPLAY_ON


If RWIDGET.DISPLAY_ON is used to display a hidden item, it must be followed by either
RWIDGET.TURN_ON or RWIDGET.TURN_OFF to enable/disable the item.
RWIDGET Variants and the Item Properties Changed:
Insert Mouse Visual
Updateable Navigable Enabled Displayed
Allowed Navigable Attribute
TURN_ON
Edit
Button TRUE TRUE TRUE
Button
Checkbox
Checkbox TRUE TRUE TRUE TRUE TRUE
item
Display Item
Image TRUE TRUE Edit Item
List TRUE TRUE TRUE TRUE TRUE Edit Item
OLE Object TRUE TRUE TRUE
Radio Group TRUE TRUE TRUE TRUE TRUE Edit Item
Text Item: Lang exit = 'Y'

© 2006 Enabler Page 77


Development Standards

Insert Mouse Visual


Updateable Navigable Enabled Displayed
Allowed Navigable Attribute
Text Item: Lang exit = 'N' TRUE TRUE TRUE Edit item
VBX Control TRUE TRUE TRUE
User Area TRUE TRUE TRUE
Error
Other
raised
TURN_OFF
Display
Button FALSE FALSE FALSE
Item
Display
Checkbox FALSE FALSE FALSE FALSE FALSE
Item
Display
Image FALSE FALSE
Item
Display
List FALSE FALSE FALSE
Item
OLE Object FALSE FALSE FALSE
Display
Radio Group FALSE FALSE FALSE FALSE FALSE
Item
Display
Text Item: Lang_exit = 'N' FALSE FALSE FALSE
Item
Text Item: Lang exit = 'Y'
VBX Control FALSE FALSE FALSE
User Area FALSE FALSE FALSE
Error
Other
raised
TURN_ON_CANCEL_BUT
TON
Edit
Button TRUE FALSE TRUE
Button
REENABLE
Display Item
Checkbox: VA = Display FALSE FALSE FALSE FALSE FALSE
Checkbox: VA = Other TRUE TRUE TRUE TRUE TRUE
Radio Group: VA =
FALSE FALSE FALSE FALSE FALSE
Display
Radio Group: VA = Other TRUE TRUE TRUE TRUE TRUE
Text Item: VA = Display FALSE FALSE TRUE
Text Item: VA = Other TRUE TRUE TRUE

© 2006 Enabler Page 78


Development Standards

Insert Mouse Visual


Updateable Navigable Enabled Displayed
Allowed Navigable Attribute
Image: VA = Display FALSE FALSE TRUE
Image: VA = Other TRUE TRUE TRUE
Button: VA = Display Same as TURN_ON
Button: VA = Other Same as TURN_OFF
List: VA = Display Same as TURN_ON
List: VA = Other Same as TURN_OFF
Other Same as TURN_ON
DISPLAY_OFF FALSE
DISPLAY_ON*
If Visual Attribute =
FALSE FALSE FALSE FALSE FALSE TRUE
TEXT_ITEM_OFF
If Visual Attribute =
FALSE FALSE FALSE FALSE FALSE TRUE
CHECKBOX_OFF
All Others TRUE TRUE TRUE TRUE TRUE TRUE
TURN_ON_MULTI**
Enabled = FALSE Edit Item
Other Similar to TURN_ON
TURN_ON_MULTI_NON_
NAV_BUTTON**
Enabled = FALSE Edit Item
Similar to
Other TURN_ON_NON_NAV_BUTTO
N
TURN_OFF_VIEW_MODE
Display
Button TRUE FALSE FALSE
Item
Display
Checkbox FALSE FALSE TRUE FALSE FALSE
Item
Display
Image FALSE FALSE TRUE
Item
Display
List TRUE FALSE FALSE
Item
OLE Object TRUE FALSE FALSE
Display
Radio Group FALSE FALSE TRUE FALSE FALSE
Item
Display
Text Item: Lang_exit = 'N' FALSE FALSE TRUE
Item

© 2006 Enabler Page 79


Development Standards

Insert Mouse Visual


Updateable Navigable Enabled Displayed
Allowed Navigable Attribute
Text Item: Lang exit = 'Y' TRUE
VBX Control TRUE FALSE FALSE
User Area TRUE FALSE FALSE
Error
Other
raised
TURN_ON_SINGLE_RECO
RD_LOV
Edit
Button FALSE FALSE TRUE
Button
Error
Other
raised
TURN_ON_MULTI_RECO
RD_LOV
Edit
Button FALSE TRUE TRUE
Button
Error
Other
raised
*DISPLAY_ON determines the item type and checks its Visual Attribute. If the item type is 'DISPLAY
ITEM', it simply turns the Display property to ON. If the item type is not 'DISPLAY ITEM' then it
checks the Visual Attribute. If the Visual Attribute indicates the item should not be editable, then
all the necessary properties are set to FALSE. Otherwise, all the attributes are set to TRUE,
displaying the item and completely enabling it.
**TURN_ON_MULTI and TURN_ON_MULTI_NON_NAV_BUTTON are intended for use with
Multiview and first ensure that the item is displayed, then perform a TURN_ON or a
TURN_ON_NON_NAV_BUTTON accordingly.

7.36 Subclassing in Forms


All blocks, triggers, and program units that appear with a red arrow on the icon in the Object
Navigator should never be changed. The red arrow indicates these components are subclassed
from a reference form. Making changes to them will break the link to the source element, and
render subclassing useless.

© 2006 Enabler Page 80


Development Standards

7.37 System Date


Oracle databases keep an internal clock. For Oracle Retail’s purposes, however, we cannot use
Oracle’s date information. This is because when our system is running at a client, after-hours
processing of the day’s transactions keeps running past midnight – into the next calendar day.
Thus, the business day extends into the next calendar day. If these after-midnight processes were
to use Oracle’s date there would be confusion as to when certain things transpired. To alleviate
confusion, our programs disregard Oracle’s date and instead use information from Oracle Retail’s
own table called PERIOD. Specifically, we use the column VDATE (virtual date). The value in this
column is updated by an Oracle Retail batch program at the end of each night’s processing.
To access the value in PERIOD.VDATE we have built a function called GET_VDATE which returns
the value for that field. Whenever a program needs to use the current date a simple call to
GET_VDATE is all that is needed:
DECLARE
L_vdate PERIOD.VDATE%TYPE := GET_VDATE;

In the example above, our program unit now holds the current date (for Oracle Retail’s purposes)
in a local variable.

7.38 Synchronize
From Oracle Forms online help:
“Synchronizes the terminal screen with the internal state of the form. That is, synchronize updates
the screen display to reflect the information that Form Builder has in its internal representation of
the screen…. Without synchronize, the screen is typically only updated when Form Builder
completes all trigger execution and comes back for user input.”
What this means:
Oracle Forms has always kept the “terminal screen” processing separate from the “internal state”
of Forms. This allowed them to create a tool to build executables which could easily be ported to
Mac, Unix, Windows, etc. Part of this legacy is that what’s on screen and what’s going on behind
the scenes aren’t always in sync. Under certain circumstances, things become unglued and require
the line synchronize in Forms code.
When should I use synchronize?
Ideally?
NEVER.
In reality?
Here are a few examples of situations in which synchronize might cure what ails your Form:
• When you have an error, but once you put in emessage or wmessage lines to try to track
the error the error goes away. Solution: replace your emessage or wmessage lines with
synchronize.
• When queried, a radio group takes the default value rather than the queried value.
Solution: use synchronize immediately before Execute_Query.
• synchronize swapping canvases in and out of a Form the cursor gets lost. Solution: use
synchronize after the Hide_Canvas and/or Show_Canvas calls.

© 2006 Enabler Page 81


Development Standards
• When switching between windows focus gets lost. Solution: use synchronize before/after
the Show_Window/Hide_Window commands.

7.39 :system Variables


Oracle Forms maintains values for a number of :system variables that provide useful information
to the developer. The values held by :system variables are in ALL CAPS. It is critical when writing
conditional statements using :system variables to evaluate them using ALL CAPS.
Correct usage example:
if :system.current_block != 'B_HEAD' then
Go_Block('B_head');
end if;

Incorrect usage example:


if :system.current_block != 'B_head' then
Go_Block('B_head');
end if;

In the second (incorrect) example, the :system.current_block will never equal 'B_head' because
'B_head' contains lowercase letters (it might contain the value 'B_HEAD', which is different from
'B_head'). In this example, therefore, the line Go_Block('B_head') will always be executed,
defeating the purpose of having it inside a conditional statement.

7.39.1 :system.record_status
Forms keeps track of the record that is currently active in the Form and assigns one of four values
(QUERY, NEW, CHANGED, INSERT) to the :system.record_status based upon whether the data is
on the database and whether the data has been changed:

Data is ON the Database

YES NO
Data HAS been modified

NO QUERY NEW

YES CHANGED INSERT

In a multi-record block we can use the :system.record_status inside a WHEN-NEW-RECORD-


INSTANCE trigger to enable and disable items based on whether the data is new or old. For
example, at Oracle Retail we prohibit the editing of Primary Key values once those values are
written to the database:
if :system.record_status in ('QUERY', 'CHANGED') then
RWIDGET.TURN_OFF('B_lc_comp.comp_no');
else
RWIDGET.TURN_ON('B_lc_comp.comp_no');

© 2006 Enabler Page 82


Development Standards
end if;

7.39.2 :system.form_status
The Form itself, when run, has a status value which varies based-upon the data’s state. There are
three possible values:
• NEW
NEW status occurs when the Form shows no base-table data. This happens before data is
queried into any base table blocks and if Execute_Query returns no rows.
• QUERY
The data in the base-table block(s) is identical to the data on the table(s). The Form will be in
QUERY status after an Execute_Query is performed if the query returns records. The Form will
stay in QUERY status until a change is made to base-table data in the Form.
• CHANGED
Data in the base-table block(s) has been updated, deleted, or inserted. The status will remain
CHANGED until a POST, ROLLBACK or COMMIT has been issued.
At Oracle Retail the :system.form_status is primarily used to determine the success or failure of a
POST or a COMMIT. If the user enters or modifies data in a Form the :system.form_status will
change from QUERY to CHANGED. This is because the base-table data in the Form no longer
matches the data on the table (the data has been changed). If, in the Form’s logic, a POST or
COMMIT is then executed, the status should return to QUERY since the base-table data will now
match the table’s data. If, following a POST or COMMIT, the status does not return to QUERY, then
something must have gone wrong.
For example:
POST;
if :system.form_status != 'QUERY' then
something went wrong so we must deal with the error…
else
WINDOW_HANDLER.SET_FORM_CHANGED;
end if;

In P_EXIT.SAVE_FORM (the standard Form-exiting code in our template) you can see the following:
Commit_Form;
---
if :system.form_status != 'QUERY' then
emessage('COMMIT_FAILURE');
raise FORM_TRIGGER_FAILURE;
end if;

Again, if the Commit_Form had executed successfully the status would be QUERY. Since the status
is not QUERY, something must have gone wrong with the COMMIT. In the case above we then
present an error message and stop the processing.

7.40 Trigger Scope


Triggers can be placed at three different levels within a Form:
Form-Level
Block-Level

© 2006 Enabler Page 83


Development Standards
Item-Level

A Form-Level trigger is active and waiting to be triggered from any location in the Form. It doesn’t
matter what block the cursor is on or what item the cursor is on. The Form-Level trigger will fire if
the triggering condition arises.
A Block-Level trigger, on the other hand, will only fire if the appropriate circumstance arises while
the cursor is on an item in the block that has the trigger.
Likewise, an Item-Level trigger will only fire if the appropriate circumstance arises while the cursor
is on the specific item to which the trigger is attached.
If the same type of trigger exists at more than one level of scope then, given where your cursor is,
only the trigger that is most specific will fire: Item-Level is more specific than Block-Level, Block-
Level is more specific than Form-Level.
For example, in the Form below there is a KEY-UP trigger at the Form-Level, another KEY-UP
trigger at the block-level on the block B_MY_BLOCK, and still another KEY-UP trigger at the item
level on the item MY_ITEM.

Case 1: The cursor is on the item B_another_block.still_another_item


The item itself does not have a KEY-UP trigger nor does its block. Therefore the KEY-UP trigger at
the Form-level fires.
Case 2: The cursor is on the item B_my_block.another_item
The item itself does not have a KEY-UP trigger. However, the block it is on has a KEY-UP trigger and
the Form itself has a KEY-UP trigger. In this case the trigger which is most narrow in scope (the
block’s KEY-UP trigger) is the one which will fire. The Form’s KEY-UP trigger is ignored.

© 2006 Enabler Page 84


Development Standards
Case 3: The cursor is on the item B_my_block.my_item
The item has a KEY-UP trigger as does its block and the Form itself. As always, the trigger which is
most narrow in scope (the item’s KEY-UP trigger) is the one which will fire. The block and the
Form’s KEY-UP triggers are ignored.

7.41 Trigger Usage at Oracle Retail


There are over 110 triggers defined that can be used within Oracle Forms. At Oracle Retail,
however, there are only 32 triggers that should ever be used in Forms. There are always
exceptions to rules, but unless your Form has special processing that no other Form in the system
performs (the logon form, for example), you should only use these 32 triggers.

7.41.1 Interface Event Triggers


Interface Event triggers fire in response to a specific action performed by the user.
WHEN-BUTTON-PRESSED
Fires when the user selects a button by clicking on it with a mouse or using the keyboard.
WHEN-CHECKBOX-CHANGED
Fires when the user changes the state of a checkbox, by clicking with the mouse or using the
keyboard.
WHEN-LIST-CHANGED
Fires when an end user selects a different element in a list item or de-selects the currently
selected element.
WHEN-MOUSE-DOUBLECLICK
Fires after the user double-clicks the mouse if one of the following events occurs:
• If attached to the form, when the mouse is double-clicked on any canvas or item in the form.
• If attached to a block, when the mouse is double-clicked on any item in the block.
• If attached to an item, when the mouse is double-clicked on that item.
WHEN-RADIO-CHANGED
Fires when the user selects a different radio button in a radio group, or deselects the currently
selected radio button, either by clicking with the mouse or using the keyboard.
WHEN-WINDOW-ACTIVATED
Fires when a window is made the active window. This occurs at form startup and whenever a
different window is given focus. Note that on some window managers, a window can be activated
by clicking on its title bar. This operation is independent of navigation to an item in the window.
Thus, navigating to an item in a different window always activates that window, but window
activation can also occur independent of navigation.
WHEN-WINDOW-CLOSED
Fires when the user closes a window.

© 2006 Enabler Page 85


Development Standards

7.41.2 Key Triggers


Key triggers fire in response to a keystroke performed by the user.
KEY-Fn (F0 – F9)
A Key-Fn (F0 – F9) trigger fires when the user presses the associated key.
You can attach Key-Fn triggers to 10 keys or key sequences that normally do not perform any Form
Builder operations. These keys are referred to as Key-F0 through Key-F9. Before you can attach
key triggers to these keys, you or the DBA must use the program Oracle Terminal to map the keys
to the appropriate functions.
KEY-OTHERS
A KEY-OTHERS trigger is associated with all keys that can have key triggers associated with them
but are not currently defined by function key triggers (at any level). At Oracle Retail, KEY-OTHERS
contains the line of code NULL; which ensures that any unspecified key will do nothing when
pressed.
KEY-DOWN
KEY-DOWN is mapped to the down arrow key on the keyboard.
KEY-UP
KEY-UP is mapped to the up arrow key on the keyboard.
KEY-LISTVAL
KEY-LISTVAL is mapped to the F9 key on the keyboard. When the user presses F9, the KEY-LISTVAL
trigger fires. We DO NOT use the Key-F9 trigger.
KEY-NEXT-ITEM
KEY-NEXT-ITEM fires when the TAB key is pressed.
KEY-PREV-ITEM
KEY-PREV-ITEM fires when SHIFT + TAB are pressed.

7.41.3 Message Handling Triggers


Message handling triggers fire in response to a message from Oracle or the form during runtime.
ON-ERROR
An ON-ERROR trigger fires whenever Forms would normally cause an error message to display.
Having an ON-ERROR trigger overrides Oracle’s built-in error processing. At Oracle Retail, our ON-
ERROR trigger initiates our own error messaging and processing, giving us full control of error
handling in our application.
ON-MESSAGE
Fires whenever Forms would normally cause a message to display and pre-empts the message. As
with the ON-ERROR trigger, Oracle Retail has created its own more flexible messaging which
supplants Oracle’s built-in messaging.

7.41.4 Navigational Triggers


Navigational triggers fire when the focus moves from one Forms component to another.

© 2006 Enabler Page 86


Development Standards
WHEN-NEW-FORM-INSTANCE
WHEN-NEW-FORM-INSTANCE is the very first trigger to fire when you launch a Form. We always
use this trigger to call P_FORM_STARTUP and put our Form start-up logic in that procedure rather
than here in the trigger.
At form startup, Forms navigates to the first navigable item in the first navigable block. The WHEN-
NEW-FORM-INSTANCE trigger fires after the successful completion of any navigational triggers
that fire during the initial navigation sequence.
This trigger does not fire when control returns to a calling form from a called form.
In a multiple-form application, this trigger does not fire when focus changes from one form to
another.
WHEN-NEW-ITEM-INSTANCE
At Oracle Retail this is only used on date fields. Fires when the input focus moves to an item.
Specifically, it fires after navigation to an item, when Form Builder is ready to accept input in an
item that is different from the item that previously had focus.
WHEN-NEW-RECORD-INSTANCE
WHEN-NEW-RECORD-INSTANCE fires whenever the user switches BETWEEN records in a multi-
record block. The name of the trigger is misleading –it does not only fire when a new record is
CREATED in a block.
As Oracle puts it: “Fires when the input focus moves to an item in a record that is different from
the record that previously had input focus. Specifically, it fires after navigation to an item in a
record, when Form Builder is ready to accept input in a record that is different from the record
that previously had input focus. Also fires whenever Form Builder creates a new record.”
We use WHEN-NEW-RECORD-INSTANCE to check the :system.record_status within multi-record
blocks. We determine if the data is new data ('NEW' or 'INSERT' status) or old data ('QUERY' or
'CHANGED') status, and then use RWIDGET calls to enable/disable the items accordingly (see the
section on Multi-Record Blocks for details).

7.41.5 Transactional Triggers


Transactional triggers fire in response to database transactions.
PRE-QUERY
Fires during Execute Query processing, just before Forms constructs and issues the SELECT
statement to identify rows that match the query criteria.
At Oracle Retail we often use the PRE-QUERY trigger to give a base table item a value. Doing so
acts like a where clause, limiting what is returned by Execute_Query to those records on the table
whose value matches the value set in the PRE-QUERY. For example, if my base table block is based
on the store table, if I set the :B_store.store item to = 1234 in the PRE-QUERY, then when I
Execute_Query, only the information for store 1234 will be put into the block.
POST-QUERY

© 2006 Enabler Page 87


Development Standards
POST-QUERY fires when the line Execute_Query is run if the block whose query is executed has
this trigger on it. Upon querying data from the database Oracle fires the POST-QUERY trigger once
for each record retrieved from the table. Thus, if the query returns 3 records from the table, POST-
QUERY will fire 3 times.
As the name implies, POST-QUERY fires after the row of data is retrieved from the database. This
means that the base table items on the block are populated with values from the table before this
code executes. So, in our case, we can use :block.item references in our POST-QUERY and use their
values programmatically.
POST-QUERY is typically used to retrieve peripheral data that is not on the table the block is based-
upon, but is in some way directly related to it. For example, the order header table (ordhead)
contains the numeric value for either a store or a warehouse. A forms block based on ordhead
would likely use a POST-QUERY trigger to take that numerical value and use it to grab the name of
the store or warehouse from either the store table or the wh table.
PRE-INSERT
PRE-INSERT fires when either a POST or COMMIT is issued in the Form and there is new
information to be inserted into the database. As Oracle puts it, “Fires during the Post and Commit
Transactions process, before a row is inserted. It fires once for each record that is marked for
insert.”
Upon POSTing or COMMITting, any records that have been added to a block (records whose
:system.record_status = 'INSERT') will be written to the database one by one. As Forms inserts
each record into the database it first looks to see if the corresponding block has a PRE-INSERT
trigger. If there is a PRE-INSERT trigger, the code in the trigger will be executed prior to inserting
the new record into the database.
At Oracle Retail we typically use PRE-INSERT for any functionality that must be performed before
the record is inserted. An example of this would be the attribution of the SYSDATE to the field
CREATE_DATETIME.
On multi-record blocks we also use PRE-INSERT to check before inserting that we have not just
entered new records that are duplicates of each other. Oracle writes each new record to the table
individually, thus PRE-INSERT fires once for each new record sequentially. Because PRE-INSERT
fires individually and sequentially for each new record, it will catch the mistake if a user enters the
same new information twice between COMMITs. See the section on Multi-Record blocks for
details.
POST-INSERT
Fires during the Post and Commit Transactions process, just after a record is inserted. It fires once
for each record that is inserted into the database during the commit process.
PRE-UPDATE
Fires during the Post and Commit Transactions process, before a row is updated. It fires once for
each record that is marked for update.
POST-UPDATE
Fires during the Post and Commit Transactions process, after a row is updated. It fires once for
each row that is updated in the database during the commit process.
PRE-DELETE

© 2006 Enabler Page 88


Development Standards
PRE-DELETE fires when either a POST or COMMIT is issued in the Form if records have been
deleted. From Oracle: “Fires during the Post and Commit Transactions process, before a row is
deleted. It fires once for each record that is marked for deletion.”
An example of a use for the PRE-DELETE trigger would be to delete child records before deleting
the parent record.
PRE-DELETE does NOT fire right when Delete_Record happens, it waits until there is a POST or
COMMIT. Upon POSTing or COMMITting, any records that have been deleted will be removed
from the database one by one. As Forms deletes each record it first looks to see if the
corresponding Block has a PRE-DELETE trigger. If there is a PRE-DELETE trigger, the code in the
trigger will be executed prior to deleting the record from the database.
POST-DELETE
Fires during the Post and Commit Transactions process, after a row is deleted. It fires once for
each row that is deleted from the database during the commit process.
ON-INSERT
Replaces the default Form Builder processing for handling inserted records during transaction
posting.
ON-UPDATE
Replaces the default Form Builder processing for handling updated records during transaction
posting.
ON-DELETE
Replaces the default Form Builder processing for handling deleted records during transaction
posting.

7.41.6 Validation Triggers


Validation triggers fire immediately before focus moves away from a field (before navigational
triggers fire) if the contents of the field have been changed.
Note that validation triggers automatically check to see that the entered value is correct for the
data type held in the field. Additional checks must be explicitly written in the validation trigger.
WHEN-VALIDATE-ITEM
WHEN-VALIDATE-ITEM fires under a number of circumstances, all of which are ways in which the
user indicates that he/she has completed entering information into an item. Such instances
include: the user switching between records, the user switching to a different item, and the user
hitting the ENTER or TAB key.
WHEN-VALIDATE-ITEM does not fire if the item has already been validated and hasn’t changed
value since it was last validated.

© 2006 Enabler Page 89


Development Standards
We use WHEN-VALIDATE-ITEM to check that the data entered meets certain required criteria. This
criteria varies from item to item based on the Form’s detailed design. It is important to remember
that we only perform the validation code if the field is NOT NULL – there is no sense in validating a
blank item. If we need to ensure that an item is not left blank, that check is performed in a WHEN-
VALIDATE-RECORD trigger for multi-record blocks, and behind the OK button on single-record
blocks. For examples of how WHEN-VALIDATE-ITEM is implemented at Oracle Retail, see the
sections on Single Record Blocks, Multi-Record Blocks, and Validation.
WHEN-VALIDATE-RECORD
WHEN-VALIDATE-RECORD fires under a number of circumstances, all of which are ways in which
the user indicates that he/she has completed entering information into any given record. Such
instances include the user switching between records and the user switching to a different block.
WHEN-VALIDATE-RECORD does not fire if the record the user is leaving has already been validated
and has not been altered since it was last validated. It only fires if the user changes data in the
record then leaves the record.
We use WHEN-VALIDATE-RECORD on multi-record blocks to check to make sure no required items
have been left blank. We do not check the data itself (that's done in various WHEN-VALIDATE-
ITEM triggers) we just make sure something is in all of the required fields. See the section on
Multi-Record Blocks for details.
Important Note on Validation Triggers:
It is important to remember that EVERY item in the Form should have the REQUIRED property
changed to 'NO' on its property palette. If REQUIRED = YES, then Forms takes over these NULL
checks for us. That would be fine except Forms gives a generic message which reads: 'Field Must
Be Entered' – it doesn't tell the user which field must be entered. So, we bypass Forms' check and
create our own with specific error messages.

7.42 Validation
All validation logic should reside in the FORM_VALIDATION local package. Each item that requires
validation should have a WHEN-VALIDATE-ITEM trigger that calls its respective procedure in the
FORM_VALIDATION package (ex. FORM_VALIDATION.ITEM).
Validation for each item should check to be sure that the validation code will only be executed if
the item is NOT NULL (if the item is NULL, there should be no need to validate):
BEGIN
if :B_head.dept is NOT NULL then
...
see if the value is valid
...
end if;
END;

Other validation rules to note:


Any setting of items to NULL should check to verify that the item is not NULL already. Also, if a
description should be set to NULL in the case of a NULL value, ensure the description is not already
NULL:
BEGIN
if :B_head.dept_name is NOT NULL then
:B_main.dept_name := NULL;

© 2006 Enabler Page 90


Development Standards
end if;
END;

• No validation should ever be performed in a navigation trigger (Key-Next, Key-Up, etc.).


• EVERY item in the Form should have the REQUIRED property set to 'NO' on its property palette.
This lets us override Forms built-in check for required fields, allowing the user greater flexibility
in how they enter data.
• In single record blocks, the record will not be validated for required fields until the Form is
committed. Read the information on Single Record Blocks in the section Block Types and Their
Triggers for additional details.
• In multi-record blocks, each record will be validated for required fields whenever the user
leaves the current record using the WHEN-VALIDATE-RECORD trigger. This trigger should check
to verify each required field is NOT NULL:
if :B_head.store is NULL then
emessage('STORE_REQUIRED');
raise FORM_TRIGGER_FAILURE;
end if;

NOTE: The only thing the WHEN-VALIDATE-RECORD trigger should do is check for NULLs. The
validation of the values the items have takes place in the WHEN-VALIDATE-ITEM trigger, not here.
Read the information on Multi-Record Blocks in the section Block Types and Their Triggers for
additional details.

7.43 WINDOW_HANDLER
If you open a document in any Windows application (e.g. Microsoft Word), make some changes,
then click on the in the upper-right corner, a pop-up window appears and asks "Do you want to
save the changes you have made?" The reason for the message is that we cannot be sure whether
the user intends to save or discard their changes when they choose to exit an application by
pressing the .
Oracle Forms operates the same way. If the user clicks on the in the upper-right corner of a
Form, Oracle checks the :system.form_status. If the status = 'CHANGED' (if the user has added,
changed, or deleted information) then Forms gives the user a "Do you want to save changes”
prompt.
There is, however, a catch. If in your code you perform a POST, then the :system.form_status will
not = 'CHANGED' even though the user may have made changes to the data – POSTing returns the
:system.form_status to 'QUERY'. For example, we always POST after we delete a record in a multi-
record block. In this case, if the user clicks on the Oracle would NOT pop-up the "Do you want
to save changes?" dialogue even though there have been changes. Instead, Oracle will just close
the window and throw away what the user has done. This is bad.

© 2006 Enabler Page 91


Development Standards
At Oracle Retail we have built our own process to account for the fact that POSTing confuses
Oracle’s built-in window-closing functionality*. Any time you POST in a Form, follow it with the
line WINDOW_HANDLER.SET_FORM_CHANGED. This command (this procedure) sets a hidden flag
which tells your Form that even though the :system.form_status = 'QUERY' it should still pop-up
the "save changes" dialogue if the user clicks on the . The status of the aforementioned flag is
checked in the standard WHEN-WINDOW-CLOSED code (see the section on Closing a Window for
more details).
* Technically, Oracle makes this check when the Form is exited – not when a window is closed. For
our purposes at Oracle Retail we have moved the logic into the WHEN-WINDOW-CLOSED trigger.
Usage Tips:
In order for the “Do you want to save your changes?” question to appear appropriately if the user
clicks on the to close a window, it is critical that WINDOW_HANDLER.SET_FORM_CHANGED be
used after every POST command.
WINDOW_HANDLER.SET_FORM_CHANGED must also be used after any changes made within the
Form other than base table changes. This includes using it after a call to any function which
performs an insert/update/delete statement.
WINDOW_HANDLER.SET_FORM_UNCHANGED is available in the event that the “Save changes”
flag needs to be reset.

7.44 Window Standards


7.44.1 Adding the Toolbar to your Window
In your Form, all main windows (that are not referenced windows and are not modal windows)
should have the following Window-level properties set:
• Horizontal Toolbar Canvas set to C_TOOLBAR.
• Height: add 20 to your Window’s height to accommodate the Toolbar canvas. After doing so,
verify that the Form still fits when run over the web and make adjustments if necessary.
If there is more than one window in the Form that requires the toolbar:
1. Create another window and rename it W_DUMMY.
2. Open the property sheet for the canvas C_TOOLBAR and change its Window property to
W_DUMMY.
3. Delete the window W_DUMMY.
4. Any time a different window is displayed that should have the Toolbar attached, add the
following line into the code:
Show_View('C_TOOLBAR');

If the instructions above do not work for your Form, do the following:
Open the property sheet for the canvas C_TOOLBAR and change its Window property to be the
first window that is displayed when the Form is run.

© 2006 Enabler Page 92


Development Standards

7.44.2 Pop-Up Windows


There is a new property class called PC_POPUP_WINDOW. This property class exists for web
purposes only. It will determine whether or not the Inherit Menu property is set to true or false.
For web users, any pop-up window will want to have this set to false, so the property class should
be attached to all pop-up windows.

© 2006 Enabler Page 93


Development Standards

© 2006 Enabler Page 94


Development Standards

8 Pro*C
The standards detailed below should be followed when designing and coding Pro*C programs:

8.1 General Standards


• Indent 3 spaces within each function, conditional statement or loop (DO NOT use tab to indent
code, instead of use 3 spaces);
• Hard-coding of variables, particularly username and password should NEVER be carried out in
production Pro*C programs. If this is practice during development, it must not be implemented
in live, and should really be avoiding during development!
• Argument counts should be checked and verified in the main section of a Pro*C program, to
ensure that the usage is correct. If argument count is incorrect, correct usage should be
printed, and the program should return failure (FAILED);
• Always ensure that a Pro*C program implements a single step in a business process. This
ensures that a module can be restarted and does not require complicated manual intervention
if a failure occurs. If a process needs 2 steps, for example, then 2 programs should be written.

8.2 Include files


All Oracle Retail batch Pro*C programs must include the header files retek_2.h and sqlca.h, which
should be defined in the program immediately after the comment section at the beginning.
Additionally, SQLCODE variable should declared as long. They should be referenced as follows:
#include <retek_2.h>
EXEC SQL INCLUDE SQLCA.H;
long SQLCODE;

Within this retek_2.h header file, the following additional header files are automatically defined
and referenced:
#include <stdio.h> standard include files (/usr/include)
#include <stdlib.h> standard library functions (/usr/include)
#include <string.h> standard C library string functions (/usr/include)
#include <ctype.h> standard C types (/usr/include)
#include <memory.h> standard C general library functions (/usr/include)
#include <math.h> maths functions (/usr/include)
#include <stdarg.h> argument header file (/usr/include)
#include <std_err.h> standard variable declarations for error handling
#include <std_len.h> standard column length definitions.
#include <limits.h> standard C limits (/usr/include)
#include <common.h> library for common functions, including return codes
#include <oracle.h> library for standard Oracle functions
#include <4_5_4.h> library for calendar/dates
#include <unistd.h> function definitions and declarations(/usr/include)

The above library routines that are not standard C are provided by Oracle Retail and stored at
$MMHOME/lib/src. In addition to these, there are a number of other libraries that have been
supplied with base ORMS.

© 2006 Enabler Page 95


Development Standards

8.3 #define section


All #define variables should be in UPPERCASE, with an _ between each word. All definitions should
be aligned to ensure easy readability, for example:
#define NULL_FREQUENCY 12
#define NULL_OFFSET 3
#define NULL_JOBNAME 26

If definitions include an additional character for nts (null terminate string), they should be named
accordingly, as NULL_<name>. Furthermore, definitions not including nts should be named
LEN_<name>, as per the Oracle Retail standards.
Wherever possible, definitions from the standard Oracle Retail header files should be used, to
avoid any hard-coding or incorrect variable lengths. By referring to the std_len.h header file, many
definitions can be found for commonly used columns.
Functions within these header files should be used, as should any standard variable declarations
contained within them. For example LEN_ITEM is declared as 25, and NULL_ITEM declared as
LEN_ITEM + 1 (26), within std_len.h.

8.4 Program structure


The basic structure of a Pro*C consists of 4 functions: main, init, process and final.

8.4.1 main()
The main() function is the starting point of any C program. The only things that happen in the
main() function are:
• A connection is made to the database;
• init(), process() and final() are called to perform the work of the program;
• Messages are written to the daily log file in order to indicate the beginning and the end of the
program’s run.

8.4.2 init()
The init() function is where one-time tasks, which must happen before actual data processing, are
performed:
• Restart/Recovery is initialized and any outstanding bookmarks are retrieved;
• System-level variables and options are fetched from database;
• Input and output files are opened for reading and writing;
• Memory is allocated.

8.4.3 process()
The process() function is where the bulk of the work of a batch program is controlled:
• The driving cursor is opened and fetched from;
• Supporting functions are called to perform program-specific functions;
• Restart/Recovery is maintained by writing bookmarks to the database.

© 2006 Enabler Page 96


Development Standards
The driving cursor is a SQL cursor that defines the data to be processed by a given batch program.
The tables queried in the driving cursor and the conditions used to gather data from them
determine much of the behavior of a program and influence decisions regarding the use of the
restart/recovery and multi-threading views. The driving cursor declaration should be done inside
the process() function.
Ideally, significant processing does not occurs in the process function itself, rather it occurs in
functions called by process().

8.4.4 final()
The final() function is where loose ends in the program are tied up:
• Restart/Recovery is closed down;
• Input and output files are closed;
• Memory is released.

8.5 Program layout


The following format should be followed when coding a new Pro*C:
1. #include
2. #defines
3. Prototype
4. Main, init, process
5. Other processing sections, get...,set...,validation,...
6. Final
7. Memory allocation (size) (if needed)
8. Memory reallocation (resize) (if needed)
9. Memory release (free) (if needed)

8.6 Variables
Variable names should clearly identify its purpose.
For example: instead of naming your variables as x, y, a, use ls_wh, ls_store, ls_item.

8.6.1 Number as String


Number should be held in string variables as much as possible. As much arithmetic as possible
should be kept in SQL cursors. If a number must be manipulated in C, it should be stored as a
double in order to provide the maximum precision.

8.6.2 Identifiers vs. Quantities


Many table columns defined as numeric in Oracle Retail are not actually numbers. That is, they do
not represent a quantity or amount of any sort and no arithmetic will ever be performed on them.
Most identifiers in the system are numeric (such as store, supplier, dept) but not numbers. These
identifiers should always be fetched into strings rather than numeric types. Because Oracle can
convert a number automatically to string and vice-versa, this requires no additional code.

© 2006 Enabler Page 97


Development Standards

8.6.3 Date
Because C has no date type, Oracle dates should be always converted into string for use in batch
programs. Date string should be in the Oracle format ‘YYYYMMDD’. If a timestamp is part of the
date, the format should be ‘YYYYMMDDHH24MISS’. The ordering of fields (year, month, then day)
allows date comparison to be done in C using a simple strcmp(). Use the date mask when
retrieving the date from database and when using the date string (in a cursor, insert, update, etc).
Example:
EXEC SQL DECLARE c_init CURSOR FOR
SELECT TO_CHAR(vdate, 'YYYYMMDD')
FROM period
...
EXEC SQL FETCH c_init INTO :ps_vdate:pi_vdate_ora_ind;
...
EXEC SQL DECLARE c_unit_retail CURSOR FOR
SELECT unit_retail
FROM price_hist
WHERE item = :is_item
AND action_date < TO_DATE(:ps_vdate, 'YYYYMMDD');

8.6.4 Variables Types and Naming


Program global variables within a program should adhere to the following standard:
Variable Type/Name Comment
Char (string) variables – Precision has either been defined before being referenced, or in held
ps_<name>[precision] within the Oracle Retail standard header files
Integers – pi_<name> Types of int, short
Indicator variables –
Assign FALSE to indicator variables.
pi_<name>_ind = FALSE
Long decimal – pl_<name> Long decimal number
Double – pd_<name> Double
File – pf_<name> Output file
Local variables within a function in a program should adhere to the following standard:
Variable Type/Name Comment
Char (string) variables – Precision has either been defined before being referenced, or in held
ls_<name>[precision] within the Oracle Retail standard header files
Integers – li_<name> Types of int, short
Indicator variables –
Assign FALSE to indicator variables.
li_<name>_ind = FALSE
Long decimal – ll_<name> Long decimal number
Double – ld_<name> Double
File – lf_<name> Output file

© 2006 Enabler Page 98


Development Standards
Variables should be declared locally within the function, unless globals are required.
Embedded PL/SQL variables should be prefixes with an uppercase L to indicate local variable, in
accordance with standards for PL/SQL. Ensure that the variables are anchored to column
datatypes (where possible).
Example:
L_error_message VARCHAR2(255);
L_item ITEM_MASTER.ITEM%TYPE;

8.6.5 Exception Handling Variables and Macros


This section details common variables and macros that are used by Oracle Retail for coding of
Pro*C programs:
Variable Type/Name Comment
Long, populated by Oracle after execution of SQL and contains
SQLCODE
Oracle error number. Value is sqlca.sqlcode
Macro to check whether resource can be accessed. Value is
RESOURCE_BUSY
SQLCODE == -54
Macro to check whether a DELETE, UPDATE or FETCH returned
NO_DATA_FOUND
no rows. Value is SQLCODE == 1403
Check for a SQL statement error. This should be used after every
SQL_ERROR_FOUND embedded SQL statement in a Pro*C program.
Value is SQLCODE !=0 and SQLCODE !=1403
Variable to hold number of records processed which is
NUM_RECORDS_PROCESSED cumulative total for multiple FETCH statements and index
record count for a DML statement. Value is sqlca.sqlerrd[2]
Determines whether an INSERT statement will violate the
DUP_VAL_FOUND
primary key. Value is SQLCODE == -1

8.7 Function prototype


Function prototypes should always be declared in the declaration section, as in the following:
int init();
int process();
int check_purges(int *oi_weekly_ind,
int *oi_monthly_ind,
int *oi_half_ind,
int *oi_year_ind);

8.8 Return Codes and Values


Return codes from Pro*C programs should either be the value of a variable, or one of the standard
Oracle Retail termination codes (as defined in common.h). Usually the following return codes are
used:
• 0 (OK): the function completed without error and processing should continue normally;

© 2006 Enabler Page 99


Development Standards
• -1 (FATAL): a fatal error occurred and the calling function should also return -1, as should its
calling function and so on up to main(), where the final error messages are logged and the
program is halted;
• (NON_FATAL): a non-fatal error occurred (such as validation of an input record failed) and the
calling function should either pass this error up another level or handle the exception.

8.9 Capitalization
8.9.1 Functions
Within a Pro*C program, functions should always be lowercase and have a meaningful name. In
parameters should be specified before out parameters, and in/out parameters specified after out
parameters.
Here are some examples of function declarations
int main(int argc, char* argv[])

int init()

int insert_price_zone(char *is_wh,


char *is_wh_curr,
char *is_wh_name)

8.9.2 Variables
C variables should be lowercase.
Example:
ls_item

Embedded PL/SQL variables should have their prefixed in uppercase and the description in
lowercase
Example:
L_error_message

8.9.3 Macro Substitutions


Macro substitution should always be in uppercase:
Example:
#define TRAN_DATA_RETURN_CODE 3
char ls_location[NULL_LOC]

8.9.4 Oracle Reserved Words


Oracle reserved words and PL/SQL built-in functions names should be uppercase. All other
identifiers are in lowercase:
Example:
EXEC SQL DECLARE c_item CURSOR FOR
SELECT os.item,
SUM(ol.qty_ordered),

© 2006 Enabler Page 100


Development Standards
SUM(NVL(ol.qty_cancelled,0))
FROM ordsku os,
FROM ordloc ol
WHERE os.order_no = :ls_order
AND ol.order_no = os.order_no
AND ol.item = os.item
ORDER BY os.item;

EXEC SQL INSERT INTO edi_daily_sales


(item,
loc,
tran_date,
sales_qty,
in_transit_qty)
VALUES (:ps_item,
:ps_store,
TO_DATE(:ps_tran_date, 'YYYYMMDD'),
0,
NULL);

8.10 Brackets
Brackets begin on the line following the statement triggering their use. Indent them to the same
depth as that statement. A line containing a bracket should not contain any other statement,
although comments are acceptable:
/* Improper bracketing */

int get_promotion() {
...
if(pi_multi_prom_ind)
{
...
}
...
if(SQL_ERROR_FOUND) { sprint(err_data,”SQL error”);
return(FATAL); }
...
return(OK);
} /* end get_promotion */

/* Proper bracketing */

int get_promotion()
{
...
if(pi_multi_prom_ind)
{
...
}
...
if(SQL_ERROR_FOUND)
{
sprint(err_data,”SQL error”);
return(FATAL);
}
...
return(OK);
} /* end get_promotion */

© 2006 Enabler Page 101


Development Standards

8.11 Comments
Source code is meant to explain a process in the clearest and most explicit manner possible.
However, situations often arise in which the action being performed by a program may be so
complex or subtle (from both a technical and business standpoint) that they may require
additional explanation. Comments in a program exist to help the reader of that program to
understand sections of code when the function or logic may not be obvious.
The following rules should be observed:
• Comments must be written clearly;
• Place the comments on the line before the code they describe;
• If a comment spans multiple lines, break it up and format it to keep it distinct from the code. Do
not use one set of comment characters to define multiple line comments;
• Do not include comments to reiterate the obvious, as in the next example:
/* Add the fetched quantity to the total quantity */
ld_total_qty += ld_fetched_qty;
• When maintaining code (fixing, customizing, enhancing) be careful to maintain all associated
comments as well. Incorrect or misleading comments can lead to serious misunderstanding and
errors. This is the primary reason why self-documenting code is desirable. Well named
variables, functions and constants as well as precise parameter passing all contribute greatly to
self-documenting code.

8.12 Handling Errors and Logging Messages


8.12.1 The Daily Log File
Every batch program should write a message to the daily log file when it starts and when finishes.
These messages are logged in the main() function.
The daily log file is kept in a directory off of the Oracle Retail batch home directory at
$MMHOME/log/. The name of the log file is the three-letter abbreviation of the month, an
underscore, and the day of the month with two digits, with ‘.log’ as an extension. Example:
Jan_05.log.
A message written to the log file has a date stamp, the name of the program and a message
stating either that the program has started, or that it has finished (successfully or not):
Mon Jan 25 18:17:26 Program: posupld: Started by rmsuser
Mon Jan 25 18:17:47 Program: posupld: Thread 1 – Terminated OK.

8.12.2 LOG_MESSAGE()
Messages are written to the log file by the LOG_MESSAGE() function. This function takes a single
string as a parameter and automatically adds the timestamp and program name.
sprintf(ls_log_message,"Thread %s - Terminated OK",ps_thread_val);
LOG_MESSAGE(ls_log_message);

© 2006 Enabler Page 102


Development Standards

8.12.3 The Program Error File


In addition to the daily log file, to which each program writes a starting and finishing message,
each program also needs to write its own error messages.
The program error file is kept in a directory off of the project’s batch home directory at
$MMHOME/error. All errors for a given program on a given day go into a single file. The name of
the program’s error file is the prefix ‘err.’, the program name, the thread number (where
applicable) and the date as a suffix. Example: err.posupld_2.Jan_05.
A message written to the program error has the program name and thread number, a time stamp,
the function where the error occurred, any related database tables, an error code (usually the
Oracle server error code number) , the Oracle error message and the program error message.
Messages are written to the error file using the WRITE_ERROR function.

8.12.4 WRITE_ERROR()
WRITE_ERROR (SQLCODE, function, table, err_data);

Parameters:
• Error code: SQLCODE (declared at the beginning of the code);
• Function: the name of the function in which the error occurred (declared at the beginning of
each function);
• Table: a list of the tables on which the error occurred;
• Error data: a formatted string describing the error conditions. This should provide reasonable
detail to users to help them figure out where the error occurred. This should not repeat any
information in the program, function or table parameters. This string should describe the action
taken, the cursor involved (if applicable) and any associated bind variable. The error message is
the only evidence on a problem and the only guide to finding that problem, so it should be as
helpful as possible.
sprintf(err_data, "UPDATE: active_date=%s", ps_vdate);
strcpy(table, "cost_susp_sup_head");
WRITE_ERROR(SQLCODE, function, table, err_data);
return(FATAL);

For function execution, the return code should be checked, and if failure, the program should
return a failure return code, as follows:
if (modify_indexes(ls_table_name, "disable") < 0)
{
return(FATAL);
}

8.12.5 Error Handling After a SQL Statement


Every embedded SQL statement should be followed by an error handling clause. This clause should
trap fatal SQL errors. If any are found, a message should be written to the program error file and
the function should return (FATAL). This signals to all parent functions that the program should be
halted.
EXEC SQL
DELETE FROM ordloc

© 2006 Enabler Page 103


Development Standards
WHERE order_no = :ps_order_no;
if (SQL_ERROR_FOUND)
{
sprintf(table, "ordloc");
sprintf(err_data, "DELETE - order_no: %s", ps_order_no);
WRITE_ERROR(SQLCODE,function,table,err_data);
return(FATAL);
}

8.12.6 Error Handling After a PL/SQL Block


Every embedded PL/SQL block should be followed by an error handling clause. This clause should
trap fatal SQL errors. If any are found, a message should be written to the program error file and
the function should return (FATAL). This signals to all parent functions that the program should be
halted.
PL/SQL errors should be handled in Pro*C by using the following:
• SQL_ERROR_FOUND – standard Oracle Retail macro for checking non-zero and non-1403 error
codes;
• Setting of li_plsql_error when a failure occurs;
• Setting of li_exception variable when an exception occurs.
Example:
int get_ref_item (char os_ref_item[NULL_ITEM],
short *oi_ref_item_ora_ind,
char is_item[NULL_ITEM])
{
char *function = "get_ref_item";
int li_plsql_error = 0;
int li_exception = 0;
char ls_error_msg[NULL_ERROR_MESSAGE] = "\0";

EXEC SQL EXECUTE


DECLARE
L_error_message VARCHAR2(255) := NULL;
L_ref_item ITEM_MASTER.ITEM%TYPE := NULL;
L_exists_ref BOOLEAN;
BEGIN
if ITEM_ATTRIB_SQL.GET_PRIMARY_REF_ITEM(L_error_message,
L_ref_item,
L_exists_ref,
:is_item) = FALSE then
:li_plsql_error := 1;
:ls_error_msg := SUBSTR(L_error_message,1,255);
else
if L_exists_ref = TRUE then
:os_ref_item := SUBSTR(L_ref_item,1,25);
:oi_ref_item_ora_ind := 0;
else
:oi_ref_item_ora_ind := -1;
end if;
end if;
EXCEPTION
when OTHERS then
:li_exception := 1;
:ls_error_msg := SUBSTR(SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,

© 2006 Enabler Page 104


Development Standards

'ITEM_ATTRIB_SQL.GET_PRIMARY_REF_ITEM',
SQLCODE), 1, 255);
END;
END-EXEC;

if (SQL_ERROR_FOUND)
{
sprintf(err_data, "STORED FUNCTION CALL FAILED: function =
ITEM_ATTRIB_SQL.GET_PRIMARY_REF_ITEM - item: %s ",is_item);
WRITE_ERROR(SQLCODE, function, "", err_data);
return(FATAL);
}
if (li_plsql_error || li_exception)
{
sprintf(err_data, "INTERNAL STORED FUNCTION ERROR: function =
ITEM_ATTRIB_SQL.GET_PRIMARY_REF_ITEM, error: %s", ls_error_msg);
WRITE_ERROR(RET_PROC_ERR, function, "", err_data);
return(FATAL);
}

return(OK);

} /* end of get_ref_item */

8.13 Conditional, Sequential Control and Loops


8.13.1 If
For the coding of if statements, the test statement should always be in round brackets (), and the
true action should be contained within curly braces {}, which will start on the line after the if
statement.
Indentation should also be applied (3 spaces), as in the following example:
if (process() < 0)
{
/*true action */
gi_error_flag = 1;
}

For multiple if statements within a block, indentation should be applied to each further statement,
as in the following example:
if (init() != NO_THREAD_AVAILABLE
{
/*true action */
if (init() == SUCCEEDED)
{
if (process() < 0)
{
gi_error_flag = 1;
}
}
}

8.13.2 GoTo
These should not be used within a Pro*C program.

© 2006 Enabler Page 105


Development Standards

8.13.3 While Loop


The test statement within a while loop should always be in round brackets (), and true action
contained within curly braces {}, commencing on the line following the while command.
Indentation should also be applied (3 spaces), as in the following example:
while (!NO_DATA_FOUND)
{
/* true action */
li_purge_ind = 0;
}

Ensure that the break statement is used to end an otherwise infinite while loop.

8.14 Cursors and Database Interaction


Database interaction must be specified with EXEC SQL. SQL keywords and commands should
always be in UPPERCASE.
Example:
EXEC SQL INSERT ... ;
EXEC SQL UPDATE ... ;
EXEC SQL DELETE ... ;
EXEC SQL EXECUTE ... ;

All queries must be done using cursors. After a cursor is opened/fetched/closed, any errors should
be checked.
Example:
EXEC SQL DECLARE c_get_unit_retail CURSOR FOR
SELECT unit_retail
FROM item_loc
WHERE loc = :ls_store
AND item = :ls_item;

EXEC SQL OPEN c_get_unit_retail;


if(SQL_ERROR_FOUND)
{
sprintf(err_data, "Opening c_get_unit_retail - loc: %s, item: %s",
ls_store, ls_item);
strcpy(table, "item_loc");
WRITE_ERROR(SQLCODE, function, table, err_data);
return(FATAL);
}

EXEC SQL FETCH c_get_unit_retail INTO :ld_unit_retail;


if(SQL_ERROR_FOUND)
{
sprintf(err_data, "Fetching c_get_unit_retail - loc: %s, item: %s",
ls_store, ls_item);
strcpy(table, "item_loc");
WRITE_ERROR(SQLCODE, function, table, err_data);
return(FATAL);
}

if(NO_DATA_FOUND)
{
ld_unit_retail = -1;

© 2006 Enabler Page 106


Development Standards
}

EXEC SQL CLOSE c_get_unit_retail;


if(SQL_ERROR_FOUND)
{
sprintf(err_data, "Closing c_get_unit_retail - loc: %s, item: %s",
ls_store, ls_item);
strcpy(table, "item_loc");
WRITE_ERROR(SQLCODE, function, table, err_data);
return(FATAL);
}

Note that no error check should be done after declaring the cursor. The cursor declaration does
not execute a SQL command and therefore, the SQL_ERROR_FOUND does not reflect the cursor
declaration command, but reflect the SQL executed before the cursor declaration.

8.15 Indicator Variables


All Pro*C variables used to return data from database should have the NULL indicator. This rule is
applied when fetching data using a CURSOR or calling a function where a Pro*C variable is used as
output parameter. When inserting a value that can be NULL, the NULL indicator should be used as
well.
The type used for NULL indicators should be short. When using, it should be prefixed with a colon
and must immediately follow its host variable.
The name should be:
<variable_scope>i_<variable_name>_ora_ind

Example:
char ps_vdate;
short pi_vdate_ora_ind;
...
FETCH c_init
INTO :ps_vdate:pi_vdate_ora_ind;

On Output:
After fetching the variable from database, the NULL indicator may have the following values:
Variable Description
-1 The column value is NULL, so the value of the host variable is indeterminate.
0 Oracle assigned an intact column value to the host variable.
>0 Oracle assigned a truncated column value to the host variable.
The integer returned by the indicator variable is the original length of the column
value, and SQLCODE in SQLCA is set to zero.
-2 Oracle assigned a truncated column variable to the host variable, but the original
column value could not be determined (a LONG column, for example).
Usually, it is used only 2 of these values:
-1 – is NULL
0 – is not NULL

© 2006 Enabler Page 107


Development Standards
Note:
If a column that contains a NULL values was fetched without an indicator variable, the program
returns the following error: “ORA-01405: fetched column value is NULL”.
On Input:
When inserting/updating, the NULL indicator may have the following values:
Variable Description
-1 Oracle will assign a NULL to the column, ignoring the value of the host variable.
>=0 Oracle will assign the value of the host variable to the column.
CURSOR example:
typedef struct
{
char (*s_order_no)[NULL_ORDER_NO];
short *i_order_no_ora_ind;
char (*s_item)[NULL_ITEM];
short *i_item_ora_ind;
double *d_qty_rcv;
short *i_qty_rcv_ora_ind;

long l_recs_returned;
long l_total_recs_processed;
long l_current_record;

} ta_ordloc;
ta_ordloc pa_ordloc;
...
while(1)
{
EXEC SQL FOR :pi_commit_max_ctr
FETCH c_ordloc
INTO :pa_ordloc.s_order_no:pa_ordloc.i_order_no_ora_ind,
:pa_ordloc.s_item:pa_ordloc.i_item_ora_ind,
:pa_ordloc.d_qty_rcv:pa_ordloc.i_qty_rcv_ora_ind;
...
for (pa_ordloc.l_current_record = 0;
pa_ordloc.l_current_record < pa_ordloc.l_recs_returned;
pa_ordloc.l_current_record++)
{
if (pa_ordloc.i_qty_rcv_ora_ind == -1)
if(receive_item() != OK)
return(FATAL);

} /* end for loop */


}

INSERT example:
EXEC SQL FOR :pi_commit_max_ctr
INSERT INTO ordloc
(order_no,
item,
qty_received)
VALUES (:pa_ordloc.s_order_no:pa_ordloc.i_order_no_ora_ind,
:pa_ordloc.s_item:pa_ordloc.i_item_ora_ind,
:pa_ordloc.d_qty_rcv:pa_ordloc.i_qty_rcv_ora_ind);

© 2006 Enabler Page 108


Development Standards
PL/SQL example:
...
char ls_order_no[NULL_ORDER_NO] = "\0";
short li_order_no_ora_ind = 0;
...
EXEC SQL EXECUTE
DECLARE
L_return_code VARCHAR2(5) := 'FALSE';
L_error_message VARCHAR2(255) := NULL;
BEGIN
NEXT_ORDER_NUMBER(:ls_order_no:li_order_no_ora_ind,
L_return_code,
L_error_message);
if (L_return_code = 'FALSE') then
:li_plsql_error := 1;
:ls_error_msg := L_error_message;
end if;
EXCEPTION
when OTHERS then
:li_exception := 1;
:ls_error_msg := SUBSTR(SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,
'NEXT_ORDER_NUMBER',
SQLCODE), 1, 255);
END;
END-EXEC;

8.16 Array Processing


Array processing is used in a number of Oracle Retail batch programs to ensure optimum
performance for SQL statements, by using bind variables (for insert, select, delete and update
statements).
Structures of arrays should be declared in the declaration section, and then referenced
accordingly. Where arrays are used for memory allocation, and initialization of record index and
array size, they should be housed within a separate function. An insert input array, which performs
inserts into a table based on the input array, would be done in a separate function.
Example:
for (ll_ins_ind = 0;
ll_ins_ind < ii_ins_tot;
ll_ins_ind += MAX_INSERT_ARRAY_SIZE)
{
if(ii_ins_tot - ll_ins_ind > MAX_INSERT_ARRAY_SIZE)
li_insert_chunk = MAX_INSERT_ARRAY_SIZE;
else
li_insert_chunk = ii_ins_tot - ll_ins_ind;

ls_item_temp = &(s_ril_updates.s_item[ll_ins_ind]);
ls_location_temp = &(s_ril_updates.s_location[ll_ins_ind]);
ls_loc_type_temp = &(s_ril_updates.s_loc_type[ll_ins_ind]);
ls_change_type_temp = &(s_ril_updates.s_change_type[ll_ins_ind]);

EXEC SQL FOR :li_insert_chunk


INSERT INTO repl_item_loc_updates
(item,
supplier,
origin_country_id,

© 2006 Enabler Page 109


Development Standards
location,
loc_type,
change_type)
VALUES(:ls_item_temp,
NULL,
NULL,
:ls_location_temp,
:ls_loc_type_temp,
:ls_change_type_temp);

if (SQL_ERROR_FOUND)
break;
}

if (SQL_ERROR_FOUND)
{
sprintf(err_data,"ARRAY INSERT - item: %s, loc: %s",
ls_item_temp[NUM_RECORDS_PROCESSED],
ls_location_temp[NUM_RECORDS_PROCESSED]);
strcpy(table,"REPL_ITEM_LOC_UPDATES");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(FATAL);
}

When an array is used on insert/update/delete, all variables used in the statement must be within
the array, i.e. no single variable can be used.

8.17 Memory Allocation


There are a number of memory allocation macros that are defined by Oracle (see common.h). The
actual function performed by the macro should be defined in the Pro*C. The base macros that are
declared are CALLOC, REALLOC and MALLOC. They may be used as by appending the variable type
as follows:
CALLOC_STRING, CALLOC_DOUBLE, CALLOC_SHORT, CALLOC_INT, CALLOC_LONG

Examples:
1) CALLOC_LONG(iot_size_this->l_ib_days_to_event, il_size);
2) CALLOC_STRING(iot_size_this->s_rr_rowid, il_size, NULL_ROWID);
3) CALLOC_SHORT(iot_size_this->i_rr_rowid_ind, il_size);

Use of REALLOC or MALLOC is similar to CALLOC, for example:


REALLOC_STRING(pa_fixed_deal.s_deal_id, *il_array_size, NULL_DEAL_ID);

FREE may also be used, which de-allocates the space pointed to by the pointer to space previously
allocated by CALLOC, MALLOC or REALLOC.

© 2006 Enabler Page 110


Development Standards

8.18 Restart Recovery and Multi-Threading


Restart recovery processing must conform to standard Oracle Retail restart recovery, using the
same functionality to restart as base programs. It is provided in the majority of Oracle Retail Pro*C
programs, and allows a program to be restarted from its point of failure. Commit rates are set
based on the RESTART_CONTROL database table. Upon each commit, information based on restart
recovery driver is written to the RESTART_BOOKMARK table. After successful completion, records
are deleted from the RESTART_BOOKMARK table. Whilst no programs are running, there should
be no records in this table, unless a program has failed. Any failure of a thread or program with
restart recovery must provide the person fixing the error with the data context, the exact point of
failure and the thread number. After the data or code issue has been rectified, the thread may
then be restarted and re-run to complete the program.
The status of each program is written to the RESTART_PROGRAM_STATUS table, and history is
stored in the RESTART_PROGRAM_HISTORY table.
Restart views should be used for query-based programs that are multi-threaded, and separate
views should be created for each query driver (for example, store and dept). The view must be
created, based on the driver name, number of threads, driver value and thread value, where the
driver value is the driving parameter (for example supplier, or store), and the remaining attributes
are held in the RESTART_CONTROL table. Here is an example of a view created for restart
recovery, which would be used to thread a program by department:
CREATE OR REPLACE VIEW V_RESTART_DEPT
(driver_name, num_threads, driver_value, thread_val)
AS
SELECT /*+ NO_MERGE */ distinct rc.driver_name driver_name,
rc.num_threads num_threads,
d.dept driver_value,
restart_thread_return(d.dept,
rc.num_threads) thread_val
FROM restart_control rc,
deps d
WHERE rc.driver_name = 'DEPT';

For examples of restart recovery and multi-threading, refer to Oracle Retail batch programs, such
as supcnstr.pc or sitmain.pc.
An example of a driving cursor from a program containing restart recovery and multi-threading
can be seen below:
EXEC SQL DECLARE c_item_loc CURSOR FOR
SELECT ph.loc,
ph.item
FROM price_hist ph,
v_restart_store rv
WHERE ph.tran_type IN (4,11)
AND ph.loc_type = 'S'
AND rv.driver_value = ph.loc
AND rv.num_threads = TO_NUMBER(:ps_restart_num_threads)
AND rv.thread_val = TO_NUMBER(:ps_restart_thread_val)
AND ph.loc > NVL(:ps_restart_loc, -9999)
OR (ph.loc = :ps_restart_loc
AND (ph.item > :ps_restart_item))
ORDER BY 1, 2;

© 2006 Enabler Page 111


Development Standards
Alternatively, multi-threading can be coded in the following way:
EXEC SQL DECLARE c_item_loc CURSOR FOR
SELECT ph.loc,
ph.item
FROM price_hist ph
WHERE ph.tran_type IN (4,11)
AND ph.loc_type = 'S'
AND MOD(ph.loc,TO_NUMBER(:ps_restart_num_threads)) + 1 =
TO_NUMBER(:ps_restart_thread_val)
AND ph.loc > NVL(:ps_restart_loc, -9999)
OR (ph.loc = :ps_restart_loc
AND (ph.item > :ps_restart_item))
ORDER BY 1, 2;

Restart/recovery can also be automatically implicit in the code in some situations, without using
restart variables. For example, when the driving cursor is based on a table whose records are in
status ‘A’ and the status is updated to status to ‘C’ after each commit. Thus, if the program fails,
when re-run it will only process records that remain in a status of ‘A’, and will not have to re-
process all data.
Number of threads per program are held within RESTART_CONTROL, and multi-threading is done
based on multiple files being processed in parallel. For example, if posupld receives 20 files, and 20
threads were configured, each thread would process one file. Note that multiple processes will not
process the same file, as they have to be split before.

8.19 Standard Calls


Detailed below are the Oracle Retail standard calls for functions that should be incorporated into
Pro*C programs, for Oracle Retail batch processing.
For standard restart recovery logic, here are some of the standard calls that may be used:
Call Description
Sets up restart control at the start of a program.
int retek_init( Pass in num_args as the number of elements in the
int num_args, init_parameter array, then the init_parameter array, then
init_parameter
*parameter, variables a batch program needs to initialize in the order and
... types defined in the init_parameter array.
); Note that all int and long variables need to be passed by
reference.
Check and commit if needed.
Pass in num_args, then variables for start_string first, and those
int retek_commit( for image string (if needed) second. The num_args is the total
int num_args,
... number of these two groups. All are string variables and are
); passed in the same order as in retek_init();
Check if commit point reached (counter check and, if table-
based, start string comparison); and commit/flush files.
int commit_point_reached( Determines if commit point has been reached.
int num_args,
... Pass in num_args, then all string variables for start_string in the
); same order as in retek_init(). The num_args is the number of

© 2006 Enabler Page 112


Development Standards

Call Description
variables for start_string. If no start_string (as in file-based),
pass in NULL.
Force a commit.
int
retek_force_commit(int Pass in num_args, then variables for start_string first, and those
num_args, for image string (if needed) second. The num_args is the total
...
);
number of these two groups. All are string variables and are
passed in the same order as in retek_init();
Close restart/recovery at end of program.
int retek_close(
If errors, rollback all database changes, otherwise Commit
void
); work;
Close all opened file streams.
int is_new_start(
void Check if current run is a new start; if yes, return 1; otherwise 0.
);
int set_filename(
rtk_file* file_struct,
char* file_name, Set filename into rtk_file structure used by other calls.
int pad_flag
);
int rtk_print(
rtk_file* file_struct, Output data to a file described by an rtk_file structure. File
char* format, ... writes will be tracked to allow consistent restart.
);

The following function may be used when working with files:


Call Description
int retek_get_record(
void * struct_ptr,
int struct_size, Fetches a line (record) of the specified type from the file into
rtk_file * in_file, the first argument.
rtk_file * rej_file,
char * rec_type
Returns 0 for success, 1 for reject, -1 for failure.
);
int retek_write_rej(
rtk_file * rej_file, Writes the reject file from the infile.
rtk_file * in_file Returns 0 for success, -1 for failure.
);

For additional standard calls, see the supplier header files, such as intrface.h.
The following function may be used for general purpose:
Call Description

© 2006 Enabler Page 113


Development Standards

Call Description
(Macro) Matches two strings. Returns 1 if they match, 0 if not.
(wrapper around standard C function strncmp, for coding
int MATCH( clarity)
char * str,
example:
char *str
if(MATCH(ls_rec_type,"TDETL"))
);
{
null;
}
int valid_all_numeric( Validates string is numeric and at least N digits long. Decimal
char * str,
points are not supported.
int desired_len
); Returns 0 for success, 1 for reject , -1 for failure
int
valid_all_numeric_signed( Validates string is numeric and signed and at least N digits long.
char * str, Decimal points are not supported.
int desired_len
);
Returns 0 for success, 1 for reject, -1 for failure.
int left_shift( Shifts string into field, removes spaces.
char * str
); Returns 0 for success, -1 for failure.
void nullpad(
char * str,
int str_len Adds a C string terminator to end of field.
);
int valid_date( Validates input field for a date.
char * str)
; Returns 0 for success, 1 for reject, -1 for failure.
int field_has_value( Checks field is non-blank.
char * str
); Returns 0 for success, 1 for reject, -1 for failure.
int all_blank( Checks field is blank.
char * str
); Returns 0 for success, 1 for reject, -1 for failure.
void zero_pad(
int i_exp_len,
char * c_str Left Pads the number with 0.
);

© 2006 Enabler Page 114


Development Standards

© 2006 Enabler Page 115


Development Standards

9 Appendix A: Development Check-list


Here you will find a list of rules to be checked after you have finished the development. Ensure
that all rules are applied. Exception should be approved by the analyst responsible by the
development.

9.1 PL/SQL programs


PL/SQL (Packages, Procedures and Functions)
If new, the program is prefixed by <PREFIX>.
The program is indented in 3 positions.
All FUNCTIONS return BOOLEAN.
Code follows the Capitalization Standard rules.
All input parameters start with “I” and are declared as “IN”.
Example: I_last_date IN DATE.
All output parameters start with “O” and are declared as “IN OUT”.
Example: O_last_date IN OUT DATE.
All parameters (when possible) are defined with “%TYPE”.
The error message is defined as “O_error_message IN OUT VARCHAR2”.
Parameters order in the signature:
1 – O_error_message
2 – All “IN OUT” parameter
3 – All “IN” parameter
All local variables starting with “L_” and (when possible) defined as “%TYPE”.
All queries treated as cursors.
All cursors are closed.
All error messages are defined in RTK_ERRORS (script must be created for new messages),
Error messages are formatted using the function SQL_LIB.CREATE_MSG.
There is a “select ... for update nowait” to lock the record before each update and delete.
Test with locking was done for update and delete.
It is defined at least the standard EXCEPTION:
EXCEPTION
when OTHERS then
O_error_message := SQL_LIB.CREATE_MSG('RTK_ERROR_MESSAGE_ERROR',
SQLERRM,
'PACKAGE_NAME.FUNCTION_NAME',
SQLCODE);
return FALSE;
Explain Plain was executed for the SQL queries used in the code and the performance is
acceptable.

© 2006 Enabler Page 116


Development Standards

Functional requirements for the object are accomplished.


All changes are properly commented.

9.2 Oracle Forms


ORACLE FORMS
If new, the program is prefixed by <prefix>.

The program is indented in 3 positions.

Form name is visible in the Windows Title property.

Code follows the Capitalization Standard rules.

Objects in the form follow the Naming Standards rules.

Use of Data Blocks (B_head and B_action), Program Units (P_FORM_STARTUP, P_EXIT,
FORMS_VALIDATION, INTERNAL_VARIABLES) and PM_mode whenever applicable.

Correct use of the Visual Attributes for Items, LOV’s and Canvas, as well as the size of the items.

Correct use of button and LOV’s properties, regarding Functional Icon Filename (listval, calendar)
and Functional Access Keys.

Physical property Bevel = none for items in Multi-Record block.

List Items populated using P_POPULATE_LIST (data defined in CODE-HEAD and CODE_DETAIL
tables).

Dynamic labels (Organizational and Merchandise Hierarchy) converted through


DYNAMIC_HIER_CODE.

Use of rectangle to separate blocks on the canvas.

LOV’s functionality: returning data and LOV size (LOV by itself and column inside the LOV).

Navigation between the objects in the logic order, using TAB and SHIFT TAB (without navigating
in the LOVs).

All numeric fields are validated regarding:


• maximum length;
• values equal zero, negative, null and letter;
• right-aligned;
• mask is appropriated.
All date fields are validated regarding:
• date range;
• error message when date is wrong;
• mask is appropriated.
Close the form through the X in the upper-right hand corner. If any change was done on the data,

© 2006 Enabler Page 117


Development Standards
the message should appear: “Do you want to save your changes?”. Check the data updating
based on the answer given to the message.

Created script for error messages (RTK_ERRORS).

Created script for List Items new values (CODE_HEAD and CODE_DETAIL).

Created script to insert the new form in the ORMS menu structure (NAV_*).

Test the form on the web environment.

Functional requirements for the object are accomplished.

All changes are properly commented.

9.3 Pro*C
PROC
If new, the program is prefixed by <prefix>.
The program is indented in 3 positions.
The required function main(), init(), process() and final() are implemented.
Program returns 0 (OK), -1 (FATAL) or 1 (NONFATAL). Exceptions are acceptable only if defined in
the requirement.
All defined variables indicate clearly their purpose.
All variables are under the standard naming convention.
First letter – Scope
g – local variables declared out of the program.
p – local variables declared in the program.
l – local variables in a function.
i – input parameter for a function.
o – output parameter for a function.
io – input/output parameter for a function.
Second letter – Type
i – Integer or Short
l – Long
d – Double
c – Caracter
s – String
a – array or estrutura de array
sf – file pointer
Cursor name are prefixed with ‘c’ lowercase.
Internal variables in PL/SQL blocks start with ‘L’ capitalized.
PL/SQL variables have the first letter capitalized and the remaining letter in lowercase.

© 2006 Enabler Page 118


Development Standards

Oracle reserved words and PL/SQL functions are capitalized.


All C variables are defined only in lowercase.
After each EXEC SQL call, there is the error handling using SQL_ERROR_FOUND.
Fatal errors interrupt the process.
Non Fatal Errors are handled. If processing text file, the error lines are handled as rejected data.
Error handling was done only using WRITE_ERROR.
Variables returned from database are handled with NULL indicator (each variables must have its
own indicator defined as li_variable_name_ora_ind, including variables that return from
database packages).
Restart/Recovery variables are handled in the functions init() and retek_commit().
retek_close is being used.
The program is organized in functions.
Memory allocation/free is separated of the logic of the program.
All function created are being used and are prototyped.
The code is organized in sections:
#include
#defines
Prototype
Main, init, process
Other processing sections, get...,set...,validation,...
Final
Memory allocation (size) (if needed)
Memory reallocation (resize) (if needed)
Memory release (free) (if needed)
Restart/Recovery has been tested.
Multi-Threads have been tested.
Functional requirements for the object are accomplished.
All changes are properly commented.

9.4 Shell Script


SHELL SCRIPT
If new, the program is prefixed by <prefix>.
The code is indented in 3 positions.
Input parameters ($1, $2, $n) are transferred to local variables.
Program returns 0 (OK), -1 (FATAL) or 1 (NONFATAL). Exceptions are acceptable only if defined in
the requirement.
All defined variables indicate clearly their purpose.
Environment variables used in the program are defined between {}.

© 2006 Enabler Page 119


Development Standards
Example: ${PROGRAM}, ${SYSDATE}.
Temporary files used in the program have the symbol $$ at the end of its name to avoid
superimposing when there is more than one program instance running at the same time.
Before calling a tool (sqlplus, sqlldr, sqlrep, etc), the binary place is indicated with environment
variables. Example: ${ORACLE_HOME}/bin/sqlplus, ${ORACLE_HOME}/bin/sqlldr.
It is indicated in the first line of the program the Shell that the program is designated for.
Example: #!/usr/bin/ksh, #!/usr/bin/sh.
It is tested the read/write permission for text files used in the program. Example:
if [[ ! -w ${TEMPFILE} ]]
then
echo "You do not have write permission on ${TEMPFILE}"
exit ${FATAL}
fi
It is tested the existence of errors after calling an external tool (sqlplus, sqlldr, sqlrep). Example:
RC=$?
if [[ ${RC} != ${OK} ]]
then
echo "Error found executing ${PROGRAM} with return code ${RC}"
exit ${FATAL}
fi
It is informed to the user the correct parameters to use the program. Example:
if [[ $# != 1 ]]
then
echo "Usage: ${PROGRAM} userid/passwd"
exit ${FATAL}
fi
Stdout and Stderr outputs are sent to files or /dev/nul. Example:
${ORACLE_HOME}/bin/sqlplus @arq.sql >/dev/null 2>/dev/null
The filename indicates the program Shell. Example: program.ksh, program.sh
Functional requirements for the object are accomplished.
All changes are properly commented.

© 2006 Enabler Page 120


Development Standards

© 2006 Enabler Page 121


Development Standards

10 Appendix B: Package Example


This appendix provides an example (simple) PL/SQL package header and package body template
that can be used by a developer for the coding of a package.
Package specification:
CREATE OR REPLACE PACKAGE <PREFIX>_BANNER_SQL AS
------------------------------------------------------------------------------
< Include here the comments using the standard defined for the client >
------------------------------------------------------------------------------
-- Name : GET_BANNER_NAME
-- Purpose: Retrieves the name of the banner from the BANNER table
------------------------------------------------------------------------------
FUNCTION GET_BANNER_NAME(O_error_message IN OUT VARCHAR2,
O_banner_name IN OUT BANNER.BANNER_NAME%TYPE,
I_banner_id IN BANNER.BANNER_ID%TYPE)
RETURN BOOLEAN;
------------------------------------------------------------------------------
-- Name : DELETE_BANNER
-- Purpose: Delete the banner from BANNER table
------------------------------------------------------------------------------
FUNCTION DELETE_BANNER(O_error_message IN OUT VARCHAR2,
I_banner_id IN BANNER.BANNER_ID%TYPE)
RETURN BOOLEAN;
------------------------------------------------------------------------------
END <PREFIX>_BANNER_SQL;

Package body:
CREATE OR REPLACE PACKAGE BODY <PREFIX>_BANNER_SQL AS
------------------------------------------------------------------------------
< Include here the comments using the standard defined for the client >
------------------------------------------------------------------------------
FUNCTION GET_BANNER_NAME(O_error_message IN OUT VARCHAR2,
O_banner_name IN OUT BANNER.BANNER_NAME%TYPE,
I_banner_id IN BANNER.BANNER_ID%TYPE)
RETURN BOOLEAN IS

L_program_name VARCHAR2(64) := '<PREFIX>_BANNER_SQL.GET_BANNER_NAME';

cursor C_BANNER_NAME is
select banner_name
from banner
where banner_id = I_banner_id;

BEGIN

open C_BANNER_NAME;
fetch C_BANNER_NAME INTO O_banner_name;
---
if C_BANNER_NAME%NOTFOUND then
O_error_message := SQL_LIB.CREATE_MSG('INV_BANNER');
close C_BANNER_NAME;
return FALSE;
end if;
---
close C_BANNER_NAME;

© 2006 Enabler Page 122


Development Standards
return TRUE;

EXCEPTION
when OTHERS then
O_error_message := SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,
L_program_name,
TO_CHAR(SQLCODE));
return FALSE;
END GET_BANNER_NAME;
------------------------------------------------------------------------------
FUNCTION DELETE_BANNER(O_error_message IN OUT VARCHAR2,
I_banner_id IN BANNER.BANNER_ID%TYPE)
RETURN BOOLEAN IS

L_program_name VARCHAR2(64) := '<PREFIX>_BANNER_SQL.DELETE_BANNER';


L_table VARCHAR2(30) := 'BANNER';
RECORD_LOCKED EXCEPTION;
PRAGMA EXCEPTION_INIT(RECORD_LOCKED, -54);

cursor C_LOCK_BANNER is
select 'x'
from banner
where banner_id = I_banner_id
for update nowait;

BEGIN

open C_LOCK_BANNER;
close C_LOCK_BANNER;
---
delete from banner
where banner_id = I_banner_id;

return TRUE;

EXCEPTION
when RECORD_LOCKED then
O_error_message := SQL_LIB.CREATE_MSG('TABLE_LOCKED',
L_table,
TO_CHAR(I_banner),
NULL);
return FALSE;
when OTHERS then
O_error_message := SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,
L_program_name,
TO_CHAR(SQLCODE));
return FALSE;
END DELETE_BANNER;
------------------------------------------------------------------------------
END <PREFIX>_BANNER_SQL;

© 2006 Enabler Page 123


Development Standards

11 Appendix C: Pro*C Example


This appendix provides a Pro*C example that can be used by a developer for the coding of a new
program.
/***************************************************************************/
/* < Include here the comments using the standard defined for the client > */
/***************************************************************************/

#include <retek_2.h>

EXEC SQL INCLUDE SQLCA.H;

/* Local Defines */
long SQLCODE;
#define NULL_LOC_DESC 151

/* Restart control variables */


unsigned int pi_commit_max_ctr;
char ps_thread_val[NULL_THREAD];
char ps_num_threads[NULL_THREAD];
char ps_restart_loc[NULL_LOC];
char ps_restart_item[NULL_ITEM];

init_parameter parameter[] =
{
/* NAME ----------- TYPE ------ SUB_TYPE */
"commit_max_ctr", "uint", "",
"thread_val", "string", "",
"num_threads", "string", "",
"restart_loc", "string", "S",
"restart_item", "string", "S"
};

#define NUM_COMMIT_PARAMETERS 2
#define NUM_INIT_PARAMETERS (sizeof(parameter) / sizeof(init_parameter))

/* Program Variables */
char ps_vdate[NULL_DATE];
short pi_vdate_ora_ind;

/*****************************************************************************\
|* Local typedefs *|
\*****************************************************************************/
struct item_array
{
char (*s_cycle_count)[NULL_CYCLE_COUNT];
short *i_cycle_count_ora_ind;
char (*s_loc_type)[NULL_IND];
short *i_loc_type_ora_ind;
char (*s_location)[NULL_LOC];
short *i_location_ora_ind;
char (*s_item)[NULL_ITEM];
short *i_item_ora_ind;
char (*s_qty)[NULL_QTY];
short *i_qty_ora_ind;

uint i_recs_returned;
int i_num_records_processed;

© 2006 Enabler Page 124


Development Standards
int i_current_record;
} pa_item;

struct insert_array
{
char (*s_cycle_count)[NULL_CYCLE_COUNT];
char (*s_loc_type)[NULL_IND];
char (*s_location)[NULL_LOC];
char (*s_item)[NULL_ITEM];
char (*s_qty)[NULL_QTY];
char (*s_location_desc)[NULL_LOC_DESC];
int i_current_record;
} pa_insert;

struct update_array
{
char (*s_cycle_count)[NULL_CYCLE_COUNT];
char (*s_loc_type)[NULL_IND];
char (*s_location)[NULL_LOC];
char (*s_item)[NULL_ITEM];
char (*s_qty)[NULL_QTY];
int i_current_record;
} pa_update;

/*****************************************************************************\
|* Function prototypes *|
\*****************************************************************************/
int init();
int process();
int process_item();
int fill_loc_desc();
int post_insert();
int post_update();
int final();
int size_arrays();
int free_arrays();

/*****************************************************************************\
|* *|
|* PROGRAM IMPLEMENTATION *|
|* *|
\*****************************************************************************/
int main(int argc, char* argv[])
{
char *function = "main";
char ls_log_message[NULL_ERROR_MESSAGE] = "\0";
int li_init_results = 0;

if (argc < 2)
{
fprintf(stderr, "Usage: %s userid/passwd\n",argv[0]);
return(FAILED);
}

if (LOGON(argc, argv) < 0)


return(FAILED);

if ((li_init_results = init()) < 0)


gi_error_flag = 2;

if (li_init_results != NO_THREAD_AVAILABLE)

© 2006 Enabler Page 125


Development Standards
{
if (li_init_results == OK)
{
if (process() < 0)
gi_error_flag = 1;
}

if (final() < 0)
{
if (gi_error_flag == 0)
gi_error_flag = 3;
}
}

if (gi_error_flag == 2)
{
sprintf(ls_log_message, "Aborted in init");
LOG_MESSAGE(ls_log_message);
return(FAILED);
}
else if (gi_error_flag == 1)
{
sprintf(ls_log_message, "Thread %s - Aborted in process", ps_thread_val);
LOG_MESSAGE(ls_log_message);
return(FAILED);
}
else if (gi_error_flag == 3)
{
sprintf(ls_log_message, "Thread %s - Aborted in final", ps_thread_val);
LOG_MESSAGE(ls_log_message);
return(FAILED);
}
else if (li_init_results == NO_THREAD_AVAILABLE)
{
sprintf(ls_log_message, "Terminated - no threads available");
LOG_MESSAGE(ls_log_message);
return(NO_THREADS);
}
else
{
sprintf(ls_log_message,"Thread %s - Terminated
Successfully",ps_thread_val);
LOG_MESSAGE(ls_log_message);
}

return(SUCCEEDED);

} /* end of main() */

/*****************************************************************************\
|* INIT *|
\*****************************************************************************/
int init()
{
char *function = "init";
int li_init_return = 0;

EXEC SQL DECLARE c_init CURSOR FOR


SELECT TO_CHAR(vdate, 'YYYYMMDD')
FROM period;

© 2006 Enabler Page 126


Development Standards
/* Initialize restart/recovery */
li_init_return = retek_init(NUM_INIT_PARAMETERS,
parameter,
&pi_commit_max_ctr,
ps_thread_val,
ps_num_threads,
ps_restart_loc,
ps_restart_item);

if (li_init_return != 0)
return(li_init_return);

if (pi_commit_max_ctr > MAX_ORACLE_ARRAY_SIZE)


{
pi_commit_max_ctr = MAX_ORACLE_ARRAY_SIZE;
if (limit_commit_max_ctr(pi_commit_max_ctr) < 0)
return(FATAL);
}

/* populate global variables */


EXEC SQL OPEN c_init;
if SQL_ERROR_FOUND
{
sprintf(err_data,"Opening c_init");
strcpy(table,"period");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(FATAL);
}

EXEC SQL FETCH c_init INTO :ps_vdate:pi_vdate_ora_ind;


if (SQL_ERROR_FOUND || NO_DATA_FOUND || (pi_vdate_ora_ind == -1))
{
sprintf(err_data,"Fetching c_init");
strcpy(table,"period");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(FATAL);
}

EXEC SQL CLOSE c_init;


if SQL_ERROR_FOUND
{
sprintf(err_data,"Closing c_init");
strcpy(table,"period");
WRITE_ERROR(SQLCODE,function,table,err_data);
return(FATAL);
}

/* Allocate memory for array */


if (size_arrays() < 0 )
return(FATAL);

return(OK);

} /* end of init() */

/*****************************************************************************\
|* PROCESS *|
\*****************************************************************************/
int process()
{
char *function = "process";

© 2006 Enabler Page 127


Development Standards
char ls_location[NULL_LOC] = "\0";
char ls_item[NULL_ITEM] = "\0";
short li_ndf = 0;

EXEC SQL DECLARE c_item CURSOR FOR


SELECT sh.cycle_count,
ssl.loc_type,
ssl.location,
ssl.item,
sq.qty
FROM stake_head sh,
stake_sku_loc ssl,
stake_qty sq,
v_restart_all_locations rv
WHERE sh.stocktake_date = TO_DATE(:ps_vdate,'YYYYMMDD')
AND sh.cycle_count = ssl.cycle_count
AND ssl.loc_type = sq.loc_type(+)
AND ssl.location = sq.location(+)
AND ssl.item = sq.item(+)
AND rv.driver_value = ssl.location
AND rv.num_threads = :ps_num_threads
AND rv.thread_val = :ps_thread_val
AND (ssl.location > NVL(:ps_restart_loc,-9999)
OR (ssl.location = :ps_restart_loc
AND ssl.item > :ps_restart_item))
ORDER BY ssl.location,
ssl.item;

EXEC SQL OPEN c_item;


if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Opening c_item");
strcpy(table, "stake_head, stake_sku_loc, stake_qty");
WRITE_ERROR(SQLCODE,function,table,err_data);
return (FATAL);
}
while(1)
{
EXEC SQL FOR :pi_commit_max_ctr
FETCH c_item INTO :pa_item.s_cycle_count:pa_item.i_cycle_count_ora_ind,
:pa_item.s_loc_type:pa_item.i_loc_type_ora_ind,
:pa_item.s_location:pa_item.i_location_ora_ind,
:pa_item.s_item:pa_item.i_item_ora_ind,
:pa_item.s_qty:pa_item.i_qty_ora_ind;
if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Fetching c_item after %ld records",
NUM_RECORDS_PROCESSED);
strcpy(table, "stake_head, stake_sku_loc, stake_qty");
WRITE_ERROR(SQLCODE,function,table,err_data);
return (FATAL);
}

if (NO_DATA_FOUND) li_ndf = 1;
if (!(pa_item.i_recs_returned = NUM_RECORDS_PROCESSED –
pa_item.i_num_records_processed)) break;
pa_item.i_num_records_processed = NUM_RECORDS_PROCESSED;

for (pa_item.i_current_record = 0;
pa_item.i_current_record < pa_item.i_recs_returned;
pa_item.i_current_record++)

© 2006 Enabler Page 128


Development Standards
{
strcpy(ls_location, pa_item.s_location[pa_item.i_current_record]);
strcpy(ls_item, pa_item.s_item[pa_item.i_current_record]);

if(process_item() != OK)
return (FATAL);

} /* end for loop */

if(post_insert() != OK)
return (FATAL);

if(post_update() != OK)
return (FATAL);

if (retek_force_commit(NUM_COMMIT_PARAMETERS,
ls_location,
ls_item) < 0)
return(FATAL);

if (li_ndf) break;

} /* end while */

EXEC SQL CLOSE c_item;


if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Closing c_item");
strcpy(table, "stake_head, stake_sku_loc, stake_qty");
WRITE_ERROR(SQLCODE,function,table,err_data);
return (FATAL);
}

return(OK);

} /* end process */

/*****************************************************************************\
|* PROCESS_ITEM *|
\*****************************************************************************/
int process_item()
{
char *function = "process_item";

/* No record found in table STAKE_QTY */


if (pa_item.i_qty_ora_ind[pa_item.i_current_record] == -1)
{
strcpy(pa_insert.s_cycle_count[pa_insert.i_current_record],
pa_item.s_cycle_count[pa_item.i_current_record]);
strcpy(pa_insert.s_loc_type[pa_insert.i_current_record],
pa_item.s_loc_type[pa_item.i_current_record]);
strcpy(pa_insert.s_location[pa_insert.i_current_record],
pa_item.s_location[pa_item.i_current_record]);
strcpy(pa_insert.s_item[pa_insert.i_current_record],
pa_item.s_item[pa_item.i_current_record]);
strcpy(pa_insert.s_qty[pa_insert.i_current_record], "1");

if(fill_loc_desc() != OK)
return (FATAL);

pa_insert.i_current_record++;

© 2006 Enabler Page 129


Development Standards
}
else
{
strcpy(pa_update.s_cycle_count[pa_update.i_current_record],
pa_item.s_cycle_count[pa_item.i_current_record]);
strcpy(pa_update.s_loc_type[pa_update.i_current_record],
pa_item.s_loc_type[pa_item.i_current_record]);
strcpy(pa_update.s_location[pa_update.i_current_record],
pa_item.s_location[pa_item.i_current_record]);
strcpy(pa_update.s_item[pa_update.i_current_record],
pa_item.s_item[pa_item.i_current_record]);
sprintf(pa_update.s_qty[pa_update.i_current_record], "%ld",
atol(pa_item.s_qty[pa_item.i_current_record]) + 100);

pa_update.i_current_record++;
}
return(OK);

} /* end process_item */

/*****************************************************************************\
|* FILL_LOC_DESC *|
\*****************************************************************************/
int fill_loc_desc()
{
char *function = "fill_loc_desc";
char ls_location[NULL_LOC] = "\0";
char ls_loc_type[NULL_LOC_TYPE] = "\0";
char ls_location_desc[NULL_LOC_DESC] = "\0";
int li_plsql_error = 0;
int li_exception = 0;
char ls_error_msg[NULL_ERROR_MESSAGE] = "\0";

strcpy(ls_location, pa_item.s_location[pa_item.i_current_record]);
strcpy(ls_loc_type, pa_item.s_loc_type[pa_item.i_current_record]);

EXEC SQL EXECUTE


DECLARE
L_error_message VARCHAR2(255) := NULL;
BEGIN
if LOCATION_ATTRIB_SQL.GET_NAME(L_error_message,
:ls_location_desc,
:ls_location,
:ls_loc_type) = FALSE then
:li_plsql_error := 1;
:ls_error_msg := L_error_message;
end if;
EXCEPTION
when OTHERS then
:li_exception := 1;
:ls_error_msg := SUBSTR(SQL_LIB.CREATE_MSG('PACKAGE_ERROR',
SQLERRM,

'LOCATION_ATTRIB_SQL.GET_NAME',
SQLCODE), 1, 255);
END;
END-EXEC;

if (SQL_ERROR_FOUND)
{
sprintf(err_data, "STORED FUNCTION CALL FAILED: function =

© 2006 Enabler Page 130


Development Standards
LOCATION_ATTRIB_SQL.GET_NAME");
WRITE_ERROR(SQLCODE, function, "", err_data);
return(FATAL);
}
if (li_plsql_error || li_exception)
{
sprintf(err_data, "INTERNAL STORED FUNCTION ERROR: function =
LOCATION_ATTRIB_SQL.GET_NAME, error: %s",
ls_error_msg);
WRITE_ERROR(RET_PROC_ERR, function, "", err_data);
return(FATAL);
}

strcpy(pa_insert.s_location_desc[pa_insert.i_current_record],
ls_location_desc);

return(OK);

} /* end post_insert */

/*****************************************************************************\
|* POST_INSERT *|
\*****************************************************************************/
int post_insert()
{
char *function = "post_insert";

if(!pa_insert.i_current_record)
return(OK);

EXEC SQL FOR :pa_insert.i_current_record


INSERT INTO stake_qty
(cycle_count,
loc_type,
location,
item,
qty,
location_desc)
VALUES (:pa_insert.s_cycle_count,
:pa_insert.s_loc_type,
:pa_insert.s_location,
:pa_insert.s_item,
:pa_insert.s_qty,
:pa_insert.s_location_desc);

if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Inserting stake_qty - cycle_count: %s,
location: %s, item: %s",
pa_insert.s_cycle_count[pa_insert.i_current_record],
pa_insert.s_location[pa_insert.i_current_record],
pa_insert.s_item[pa_insert.i_current_record]);
strcpy(table, "stake_qty");
WRITE_ERROR(SQLCODE,function,table,err_data);
return (FATAL);
}

pa_insert.i_current_record = 0;

return(OK);

© 2006 Enabler Page 131


Development Standards
} /* end post_insert */

/*****************************************************************************\
|* POST_UPDATE *|
\*****************************************************************************/
int post_update()
{
char *function = "post_update";

if(!pa_update.i_current_record)
return(OK);

EXEC SQL FOR :pa_update.i_current_record


UPDATE stake_qty
SET qty = :pa_update.s_qty
WHERE cycle_count = :pa_update.s_cycle_count
AND loc_type = :pa_update.s_loc_type
AND location = :pa_update.s_location
AND item = :pa_update.s_item;

if (SQL_ERROR_FOUND)
{
sprintf(err_data, "Updating stake_qty - cycle_count: %s,
location: %s, item: %s",
pa_update.s_cycle_count[pa_update.i_current_record],
pa_update.s_location[pa_update.i_current_record],
pa_update.s_item[pa_update.i_current_record]);
strcpy(table, "stake_qty");
WRITE_ERROR(SQLCODE,function,table,err_data);
return (FATAL);
}

pa_update.i_current_record = 0;

return(OK);

} /* end post_update */

/*****************************************************************************\
|* FINAL *|
\*****************************************************************************/
int final()
{
char *function = "final";
int li_final_return = 0;

free_arrays();

li_final_return = retek_close();

return(li_final_return);

} /* end of final() */

/*****************************************************************************\
|* SIZE_ARRAYS *|
\*****************************************************************************/
int size_arrays()
{
char *function="size_arrays";
short li_no_mem = 0;

© 2006 Enabler Page 132


Development Standards

if (!li_no_mem&&(pa_item.s_cycle_count = (char (*)[NULL_CYCLE_COUNT])


calloc (pi_commit_max_ctr, NULL_CYCLE_COUNT)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.i_cycle_count_ora_ind = (short *)
calloc (pi_commit_max_ctr, sizeof(short))) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.s_loc_type = (char (*)[NULL_IND])
calloc (pi_commit_max_ctr, NULL_IND)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.i_loc_type_ora_ind = (short *)
calloc (pi_commit_max_ctr, sizeof(short))) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.s_location = (char (*)[NULL_LOC])
calloc (pi_commit_max_ctr, NULL_LOC)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.i_location_ora_ind = (short *)
calloc (pi_commit_max_ctr, sizeof(short))) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.s_item = (char (*)[NULL_ITEM])
calloc (pi_commit_max_ctr, NULL_ITEM)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.i_item_ora_ind = (short *)
calloc (pi_commit_max_ctr, sizeof(short))) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.s_qty = (char (*)[NULL_QTY])
calloc (pi_commit_max_ctr, NULL_QTY)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_item.i_qty_ora_ind = (short *)
calloc (pi_commit_max_ctr, sizeof(short))) == NULL) li_no_mem = 1;

if (!li_no_mem&&(pa_insert.s_cycle_count = (char
(*)[NULL_CYCLE_COUNT])
calloc (pi_commit_max_ctr, NULL_CYCLE_COUNT)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_insert.s_loc_type = (char (*)[NULL_IND])
calloc (pi_commit_max_ctr, NULL_IND)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_insert.s_location = (char (*)[NULL_LOC])
calloc (pi_commit_max_ctr, NULL_LOC)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_insert.s_item = (char (*)[NULL_ITEM])
calloc (pi_commit_max_ctr, NULL_ITEM)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_insert.s_qty = (char (*)[NULL_QTY])
calloc (pi_commit_max_ctr, NULL_QTY)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_insert.s_location_desc = (char (*)[NULL_LOC_DESC])
calloc (pi_commit_max_ctr, NULL_QTY)) == NULL) li_no_mem = 1;

if (!li_no_mem&&(pa_update.s_cycle_count = (char
(*)[NULL_CYCLE_COUNT])
calloc (pi_commit_max_ctr, NULL_CYCLE_COUNT)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_update.s_loc_type = (char (*)[NULL_IND])
calloc (pi_commit_max_ctr, NULL_IND)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_update.s_location = (char (*)[NULL_LOC])
calloc (pi_commit_max_ctr, NULL_LOC)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_update.s_item = (char (*)[NULL_ITEM])
calloc (pi_commit_max_ctr, NULL_ITEM)) == NULL) li_no_mem = 1;
if (!li_no_mem&&(pa_update.s_qty = (char (*)[NULL_QTY])
calloc (pi_commit_max_ctr, NULL_QTY)) == NULL) li_no_mem = 1;

if (li_no_mem)
{
sprintf(err_data, "Unable to allocate memory!");
WRITE_ERROR(RET_FUNCTION_ERR, function, "", err_data);
return(FATAL);
}

pa_item.i_recs_returned = 0;
pa_item.i_num_records_processed = 0;
pa_item.i_current_record = 0;

pa_insert.i_current_record = 0;
pa_update.i_current_record = 0;

© 2006 Enabler Page 133


Development Standards

return(OK);

} /* end size_arrays */

/*****************************************************************************\
|* FREE_ARRAYS *|
\*****************************************************************************/
int free_arrays()
{
char *function = "free_arrays";

free(pa_item.s_cycle_count);
free(pa_item.i_cycle_count_ora_ind);
free(pa_item.s_loc_type);
free(pa_item.i_loc_type_ora_ind);
free(pa_item.s_location);
free(pa_item.i_location_ora_ind);
free(pa_item.s_item);
free(pa_item.i_item_ora_ind);
free(pa_item.s_qty);
free(pa_item.i_qty_ora_ind);

free(pa_insert.s_cycle_count);
free(pa_insert.s_loc_type);
free(pa_insert.s_location);
free(pa_insert.s_item);
free(pa_insert.s_qty);
free(pa_insert.s_location_desc);

free(pa_update.s_cycle_count);
free(pa_update.s_loc_type);
free(pa_update.s_location);
free(pa_update.s_item);
free(pa_update.s_qty);

return(OK);

} /* end free_arrays */

© 2006 Enabler Page 134

You might also like