Showing posts with label Configuration. Show all posts
Showing posts with label Configuration. Show all posts

Thursday, October 22, 2015

Scriptless OK/Cancel popup

In the early days of configuring in Siebel, if a user wanted a confirmation or warning message before proceeding, it would require Browser Script to implement and most Siebel configurators would try to discourage the requirement on purely technical grounds.  And to be fair, an application littered with popup warnings may not be a great idea on functional grounds either, but there are probably good reasons to implement a warning message on occasion and it would be nice if it could be done in a way that does not have technical repercussions.  So here you go.

On a BC, configure a 'Named Method' user property with value:
"YourMethodName", "INVOKESVC", "FDNS IDENT Encounter", "LS Pharma Signature UI Service", "ShowConfirmDialog", "'Cancel Method Name'", "YourCancelMethodName", "'OK Method Name'", "YourOKMethodName", "'Confirm Text'", "'Are you sure you want to Proceed or some other message?'"

The method 'YourMethodName' would be invoked according to your requirements.  In a simple case, a custom action button on an applet could invoke this method but it could really be invoked anywhere.

The methods 'YourOKMethodName' and 'YourCancelMethodName' need to be callable methods, either that you also configured as additional Named Methods, or vanilla methods (or scripted ones defined in PreInvoke but that would sort of defeat the point). 

When 'YourMethodName' is invoked, a popup message containing the message parameter is shown with an Ok and Cancel button.  Clicking either button calls the methods defined.  Enjoy

Vanilla Merge Behavior

I recently encountered an issue when adding a DB View based EBC to a BO.  When I attempted to perform a MergeRecords operation on two records in the primary BC (Contact in this case), I got an error:

[1] An error has occurred writing to a record
Please continue or ask your system administrator to check your application configuration if the problem persists.(SBL-DBC-00111)
[2]ORA-06550: line 137, column 15:
PL/SQL: ORA-01031: insufficient privleges
ORA-06550: line 137, column 1:
PL/SQL: SQL Statement ignored

It turns out this error is caused because siebel is attempting to update a column in a DB View.  Why would it try to do that you might ask?

If we reverse engineer what is happening, we find that when performing a MergeRecords operation, Siebel determines the underlying table of the active business component and uses the SRF to find all links where the identified table is shared with the source business component of the Link and the source field is ‘Id’ (or null which is the same thing).  The merge algorithm then takes this list to write the SQL to update the appropriate destination field to the new value of the Id field on the Source BC.  Since merge is a data integrity operation, the use of Links using the ‘Id’ field is a proxy for those links configured to have a data integrity implication. 

Ideally, Siebel would provide a configurable mechanism to exclude a particular link from a Merge, or, at a minimum, to recognize that when a link points to a destination BC that is based on a table object whose type is ‘External View’, no update is possible and hence should not be attempted.  Alas that is not the case. 

Therefore a way to trick the algorithm into excluding this link is to define the link on one which is not based on data integrity, and instead make it just informational.  This can be done by making the Source field of the link something other than Id.  But since we do not want to actually change the definition of the view this link points to, a column whose value matches the ROW_ID column is desireable.  In the case of the Contact BC, there are a couple of potential options.  PERSON_UID defaults to the Id field but since this column might be populated by EIM to be a value other than it’s ultimate row id, the values may not match on that data set.  But since Contact is based on the Party model, the PAR_ROW_ID should always match since this points to the S_PARTY record and the same ROW_ID is always used. This column is not exposed on the Contact BC though so it needs to first be exposed and then the new BC field can be used in the links.

Tuesday, March 29, 2011

Tools Bleg

Don't get me wrong. I love Siebel Tools. Compared to other enterprise systems where development for the most part involves modifying script, Siebel has a very elegant development platform. OK, all that being said, after developing in Tools for over eleven years (odd writing that), there are some things I would love to do better to make my development experience more efficient. So to that end I thought I would put some thoughts out into the cloud to see if anyone has thought of a workaround for any of these items:
  • Column Preferences. Is it just me or does the Tools client not save preferences the way the Siebel client does. Rearranging columns usually works, but changing widths do not seem to save.
  • PDQs. The idea of Bookmarks is nice but I hate the fact that drilling down or using them loses the context of my exporer pane when I go back. PDQs on every object like within the Siebel Client (and the ability to set default PDQs for each view) would do wonders.
  • Drilldowns. Speaking of drilldowns, is it really necessary for drilling down to collapse the rest of my explorer pane, hence refreshing all the queries on other objects?
  • Expose Tab Order on Applets. I am tempted to try this one out myself one day because it seems doable. Who knows.
  • Applet Wizard. Not for creating a new one. That is ok. But to synchronize with a BC down the road when I want to add a new column. A wizard would just be a much easier way to add a new column rather than adding a control or list column, then adding it to the web template.
  • Allow sync of Meta Data needed by Tools without Remote Sync. This might be a bit more out there but I find it annoying that Users (Help about record) and LOVs cannot be synced with a 'Get'. I know you can get them with a remote sync, but more and more, a lot of client's do not use Remote or use it so infrequently that it is not emphasized and it is a pain to keep my remote client in sync with the server in a development environment anyway. This might sound minor, but like I said, it annoys me.
I have mainly limited this list to just applying functionality that already exists in the Siebel Client or to exposing data which I am pretty sure is there to be exposed. Not really trying to create a forum for adding "New" features. I may add to this list in the future, but feel free to add your own wishes/solutions in comments.

Thursday, January 20, 2011

Building a BI Developer's SuperView

Another limitation I find irritating when it comes to building BI templates is how basic the sample file generator is. My main beef is that it just takes the first 10 records in the Integration Object and spits them out. If you have a complicated IO with child ICs, it is possible, and even likely that those first ten records do not have the child detail records you need to test your report output. There are some ways around this, like hard coding a search spec on the BC against a thick client partial compile to generate a file with data you want, but that seems so inelegant. My other tick regarding this feature is that the report developer once again either needs to have a Siebel thick client or access to the Siebel Server file system to actually get the xml file produced. It seems like the whole point of all the BI Administration views is to avoid having to go to the file system. What to do...

Caveat Emptor. Configuration steps below are to give you an idea. I am posting this after I finished to highlight what I recall as the important pieces so not every step is included. You will need to create the new custom table CX_TMPL (or use another), create all links, applets, view objects, make BO/Screen changes and deploy them.

First I build a view with the same IO BC based applet as the vanilla view on top, and child applets for both attachments and a new object which is essentially a search spec. First the attachment bc. This is a new BC which you can copy from an existing attachment BC and change the names around. Here is mine, called 'Sample IO Attachment' based on S_FILE_ATT. Use the field name prefix 'Sample' instead of which ever prefix is used on the BC you are copying (Be sure to set the User Property DefaultPrefix to 'Sample' too):
NameJoinColumnForce ActivePredefault ValueText LengthType
IO Id
PAR_ROW_ID
Y
Parent: 'Repository Integration Object.Id'15DTYPE_ID
Parent KeyX_PARENT_KEY
Parent: 'Repository Integration Object.Name'100DTYPE_TEXT


The Search Spec applet is based on a custom BC, 'Report IO Sample File Template', based on the new table, CX_TMPL (I use this table for other things too so I type spec each record):
NameJoinColumnForce ActivePredefault ValueText LengthType
Name
NAME
Field: "Id"100DTYPE_TEXT
Parent IdS_INT_OBJROW_IDYParent: "Repository Integration Object.Id"15DTYPE_ID
Parent Name
PARENT_FLD
Parent: "Repository Integration Object.Name"50DTYPE_TEXT
Search Specification
CONSTRAINTY
250DTYPE_TEXT
Type
TYPE
SAMPLE_IO_CONSTRAINT30DTYPE_TEXT
Number of Records
LN_NUM10
DTYPE_INTEGER


The join, S_INT_OBJ, is based on the specification of Parent Name = NAME. Using name instead of Id allows the search specs to remain visible after repository moves.

You will also need the following Named Method User Property:

"GenerateConstrainedData", "INVOKESVC", "Report IO Sample File Template", "Workflow Process Manager", "RunProcess", "'ProcessName'", "'Export Sample IO To File'", "SearchConstraint", "[Search Specification]", "'IOName'", "[Parent Name]", "Path", "'..\XMLP\Data'", "Object Id", "[Parent Id]", "PageSize", "[Number of Records]"

This user property is to activate the button you will need to place on the applet based on this BC. On that applet (based on class CSSSWEFrameListBase), add a button which invokes the method 'GenerateConstrainedData'. No additional script should be needed there.

Create a Service Flow Workflow Process called 'Export Sample IO To File'


Here are the Process Properties:

NameIn/OutData Type
FileNameInString
IONameInString
PageSizeInString
PathInString
SearchConstraintInString
SiebelMessageNoneHierarchy
ViewModeInString


The first 'Echo' step is a Business Service based on Workflow Utilities, Echo method. This step sets up all the variables used later in the process. Here are the arguments:

I/OArgumentTypeValue/Property Name
InputIONameProcess PropertyIOName
InputPageSizeProcess PropertyPageSize
InputPathProcess PropertyPath
InputSearchConstraintProcess PropertySearchConstraint
InputViewModeProcess PropertyViewMode
OutputFileNameExpressionIIF([&FileName] is not null, [&FileName], [&IOName])


The next 'Export IO' step is a Business Service based on EAI Siebel Adapter, QueryPage method. This step queries the integration object. Here are the arguments:

I/OArgumentTypeValue/Property Name
InputOutputIntObjectNameProcess PropertyIOName
InputPageSizeProcess PropertyPageSize
InputSearchSpecProcess PropertySearchConstraint
InputViewModeProcess PropertyViewMode
OutputSiebelMessageOutput ArgumentSiebelMessage


The next 'Write to File' step is the Business Service, EAI XML Write to File, WriteEAIMsg method. This step writes the property set out as an XML document to the file system. Here are the arguments:

I/OArgumentTypeValue/Property Name
InputFileNameExpression[&Path]+"\"+[&Process Instance Id]+"_"+[&FileName]+".xml"
InputSiebelMessageProcess PropertySiebelMessage


The final 'Attach' step is another Business Service, this one custom. The basic logic here is to add an Attachment to the file system which is first described in Oracle document 477534.1 (I have made some improvements which I will perhaps discuss another day). Here are the arguments:

I/OArgumentTypeValue/Property Name
InputAttBusinessComponentLiteralSample IO Attachment
InputAttachmentFieldNameLiteralSampleFileName
InputBusinessObjectLiteralRepository Integration Object
InputFileExpression[&Path]+"\"+[&Process Instance Id]+"_"+[&FileName]+".xml"
InputObjectIdProcess PropertyObject Id
InputPrimaryBusinessComponentLiteralRepository Integration Object

Friday, January 7, 2011

ADM - List Of Values

There are plenty of posts on support web discussing the issues with migrating LOVs, but for my own sanity, I thought I would summarize all of the relevant issues in one place.

First, we need to address the defects. These are documented on support in Document 731411.1 but I will summarize here:
(1) Go to BC 'List Of Values Child (UDA)'
(2) Add a new field 'Parent Type' based on Join 'Parent LOV' and Column 'TYPE' with Text Length '30'.
(3) Expand the pickmap for the 'Parent' field. Replace pickmap field 'Type' with 'Parent Type' and uncheck Constrain flg.
(4) Go to the integration object 'UDA List Of Values'
(5) Find the Integration component 'List Of Values Child (UDA)'
(6) Add a new field to the integration component with Name = 'Parent Type'. Data Type = 'DTYPE_TEXT', Length = '30', Type = 'Data', External Name = 'Parent Type', External Data Type = 'DTYPE_TEXT', External Length = '30', External Sequence = '38', XML Tag = 'ParentType'
(8) Compile changes.
The SR then goes into some more detail on why after all that it still does not quite work. To understand, we need to see that the LOV ADM Integration Object is Hierarchical in one dimension. That is, there is the LOV_TYPE record and then there are the value records. But LOVs are frequently Hierarchical in two dimensions, by virtue of the Parent value. What I mean is that a given LOV value record will always have one 'parent' record, it's type or technical parent, and may have a second parent record, it's functional parent, if you will.

ADM loads the first, technical parent in the standard way, through the relationships of the Integration Object. To load the functional parent though, ADM must run in two passes, the first to create all the parent and child records, and the second to relate them. This is necessary because we cannot guarantee the sequence with which LOV value records will be placed in the extract file. If these value records do not exist in the target already, and the parent is alphabetically (or however else we chose to sort the records) after the child, then it would error if ADM did not take this approach. So how ADM takes two passes is by virtue of the ADM Data Type explorer. You will notice that the explorer does not actually specify the foreign key fields of an object to link them to each other. Its only purpose is to run ADM in multiple passes. But the twist is that ADM will actually process dependent data types setup in the explorer in reverse order, importing the children before the parent. I personally find this confusing from a terminology perspective. Perhaps a better way of naming these Data Types is to use 'LOV-2ndPass' instead of 'LOV-HierParent' and 'LOV-1stPass' instead of 'LOV-HierChild'. This way when we set up the search specifications for an ADM Export session, it is clear what we are trying to do.

OK, one more wrinkle to throw into the mix (just when you thought it was all making sense). There is actually a third parent relationship involved. That is the records that populate the S_LOV_REL table. I will be honest; I do not use the LOV explorer view that often and I don't really know what the point of this table is. In theory it can make LOVs M:M but I just don't think this is practical. Nevertheless, there are some vanilla uses of LOVs where these records are in fact used that way. The one that comes to mind is in payments, where the PAYMENT_TYPE_CODE values are children of the PAYMENT_METHOD_CODE and there are S_LOV_REL records created to store the relationships. The same issue applies when migrating these relationships. The related value must exist prior to the relationship being built.

One final note. I think the whole not deleting LOVs is well intended but more likely to cause confusion than solve anything. Here is why. Users can and will just change the Name/Value of a value record to something else in which case any sense of history is lost anyway. There are no foreign key relationships to LOVs so business data using these values is unaffected regardless. But others may disagree so this step is completely optional. I remove the no delete properties from the 'List Of Values Child (UDA)' BC and Integration Component. (I also allow deletes from the GUI but that is a separate issue). So my migration methodology is to synchronize values between environments for an initial release. You would take a different approach on a point release where values are likely to have been added directly to production and therefore may not exist in your DEV and TEST environments.

Anyway, what are we trying to do. Quite simply, we are trying to create all the Value records in pass 1, then we need to relate them to each other in Pass 2. I have already discussed how to group LOVs together for a release. This is where I diverge from Siebel's example because I am trying to think of real life scenarios where I am deploying releases, not just one LOV_TYPE. When creating the ADM Project/Session, here are the session items I use:


Data TypeChild DeleteDeployment Filter
LOV-2ndPassY[Release] = '1.1'
LOV-1stPassN[Release] = '1.1' AND [List Of Values Relationship.Name] IS NULL

What this means is that the first pass includes all LOV_TYPE records that have been marked for this release and all LOV value records related to them. The second part of the expression basically just insures that no relationship records are included in the first pass. When ADM attempts to set the parent value on a child, it may not be able to find it so it will log a warning and move on. In the second pass, ADM will load all the Relationship records and set the Parent values that it missed on the first pass. I have also set child delete to true on the second pass so that this job effectively synchronizes the value records for the type records marked.

Monday, November 15, 2010

A Basic Interface - Integration Object User Props

I know this is meant to be a basic interface with so complexity, but let's be realistic about the requirements we are likely to get. Even a simple upsert of something as basic as a service request is likely to require a bit of digging into bookshelf so that the interface is able to mimic basic GUI functionality. I will discuss some of the most commonly used User Properties necessary to implement even an advanced interface. When in doubt about the syntax of any of these properties, take a look for an example in the Tools flat view.

PICKLIST

This is the most common Integration Component Field User Property you will see and it basically tells the EAI Siebel Adapter to validate the picklist in the interface. This property is generally created by the wizard so I bring it up only because validating the picklist here will allow for several different ways to interpret a picklist field value described by some of the user properties below.

PicklistUserKeys

In the GUI, when you type an account name in the Account field on another BC that has a picklist of Accounts, and there is more than one record matching that name (with different locations), a pick applet will pop open with the constrained list of accounts having that name. The GUI is letting a user decide which of the multiple records returned was meant to be picked. An interface does not have that luxury, so the PicklistUserKeys Integration Component Field User Property is provided to mimic this action. The value of this property should be a comma separated list of fields representing the logical key of the picklist record to look up. These fields must all be present in the integration component (though there values can be null). The 'PICKLIST' user property must also exist for the field where this property is used and its value must be 'Y'.

Ignore Bounded PickList

When a picklist is validated in the interface and the value passed is not found, the EAI Siebel Adapter stops processing and returns an error. If the data is expected to sometimes be missing though, you may want the foreign key to just be left blank. For instance, maybe the service request, in our example is tied to an order via a back office order number, but the order was never loaded. Add this user property with a value of 'Y' in combination with the PICKLIST user property with a value of 'Y'. The EAI Siebel Adapter will look up the record by the user key provided (can also be used in combination with PickListUserKeys) but if it is not found, will set the field to blank in the integration object before applying the data. Keep in mind that this property will only work as expected if the Picklist object the underlying BC uses to constrain the field is set to No Insert equals True, otherwise, the EAI Siebel Adapter will try to insert a record. Also note that in bookshelf there is a typo in that there should be spaces between the words of the property name.

FieldDependency

It is easy in the GUI to determine the order of the fields being picked, either by training or by sequencing the fields in a particular way during applet design. This may help set the fields that will be used to constrain the value of another field, frequently in a hierarchical picklist. In EAI, we achieve this result through this user property. It can be used multiple times with a sequence number, just like other BC and applet user properties. The value is a field integration component field name. Siebel claims that pickmapped constraints are automatically taken into account, and that may typically be the case, but I have seen times when it does not work, so this is a good fall back.

Friday, October 29, 2010

A Basic Interface - Web Service Workflow

Just about every interface consists of two basic components: the integration object(s) and the workflow or business service. I will demonstrate a workflow approach which will give you more opportunity to customize down the road.

It is here that we begin to differentiate the integration by the communication mechanism. Because I am designating this integration as a Web Service, that will drive the type of data this workflow will expect as an input and output. The workflow I build will eventually be exposed as a WSDL to be consumed by an external program. That WSDL should have the definition of the message it is expecting, in this case, the XSD, or definition of the Integration Object we just built. How we accomplish this is to set the Input Process Property to a Data Type of 'Integration Object' and to actually specify the integration object we built, in the Integration Object attribute of the process property.


You can also see my place holder for the SR Number that I want to return to the external system in the response message. The 'IncomingXML' property is already in the format needed to be passed to the EAI Siebel Adapter, so there is no conversion necessary. And we are assuming that the data being passed is exactly as it should be applied. You will create the following steps which I will explain (other than Start and End which are self explanatory):
The 'Upsert SR' is a Business Service calling 'EAI Siebel Adapter'. Now here is the another design decision to be made. Each of the available methods differentiate exactly how the data should be applied. But there are two broad determinations. If we were to use the Execute method, then the 'operation' element which exists in each component of the IO would be used to determine how the data should be applied. This gives more control the calling system (or a data map which I will discuss later). The other set of methods essentially comprise a One Size Fit All to applying all the data uniformly. I will use the latter approach here and set the method to 'Upsert'. There is only one component in my IO, so if it exists, it will be updated, otherwise it will be inserted. The input arguments for this step are the IncomingXML message from the external system and a parameter telling the EAI Siebel Adapter to create the Status Object.

There is one Output Argument. We no longer care about the input message at this point because it will have been applied so we just overwrite it with the return, which in this case will be the status key.
The last step in the WF is another Business Service step calling 'PRM ANI Utility Service', 'GetProperty' method. This business service has a plethora of useful methods for manipulating property sets. This particular method will extract the value of a field from an integration object instance. Here are the inputs:
The output is to set the process property 'SRNumber' with the Output Argument, 'Property Value'. When the return message is sent back to the calling system, this property will exist with the generated SR Number.

Simulating/Troubleshooting this WF from within tools is difficult as built so I sometimes add a bypass step off the start branch to read the integration object from a file. I may talk about this later but want to keep this post pretty straightforward. So for now, this workflow can just be deployed, checked in and activated.

A Basic Interface - Building the Integration Object

I am not sure how easy it will be to summarize EAI in a couple of blog posts as there are definately a lot of ifs and buts in the design process. Nevertheless, I think it wold be useful to show how to build a basic interface using a couple of different techniques. Frequently you client's enterprise architecture will drive which to use.

Integration generally takes one of three forms
  • Query - Returns a data set of source data to be displayed in the target system
  • Schema Update - Takes a hierarchical data structure and applies it to the target system
  • Functional Action - Triggers a service to perform some set of business rules
There is perhaps some overlap here and any of these can be inbound or outbound to siebel, but this is a general way of categorizing your interfaces. And within each there are several different ways to implement more specific requirements.

Regardless of approach, the basic component of most interfaces is the structure of how data is viewed or applied. Let's say we need to Upsert a Service Request. A Schema Update assumes a hierarchical organization of data using the Integration Object data structure. Bookshelf provides extensive instruction on how these are built and configured to achieve certain goals so I will only touch on the highlights.

First, create an Integration Object in Siebel: from the Tools File Menu, New Object Wizard, EAI Tab, choose Integration Object. In the wizard, select the Project and choose 'EAI Siebel Wizard' from the second dropdown, and click Next. For the purpose of this example, we can just use the Service Request business object as the source object and the root BC will be Service Request. Enter a name of your choosing and click Next. In the next wizard page, deselect all child objects for which there are no fields to set. In this case that will be all of them except for the root as the more objects and fields in the message, the longer it will take the various architecture components to parse and translate the message. Click Next, then Finish on the next page.

Your Integration Object has been created. The next step is to verify the user keys. An integration object needs to have a valid user key in order to do an upsert. This basically specifies which key fields to use to find a record to update. In my example for Service Request, a key was not generated by the wizard so I will create one. Navigate to Integration Component Key in the explorer under the Service Request Integration Component. Create a new record, provide a name, set the sequence number to 1 and the key type to 'User key'. Create a child record in Integration Component Key Fields, provide a name and set the Field Name to 'Id'.

Another optional step we will use in this example is the Status Key. After creating a service request, I want to return the service request number to the external system as verification of success and so this SR can be referenced later by the customer. To do this we use the Status Key. This is basically a structure of the data set we wish to return from the EAI Siebel Adapter call and pass back to the calling system. A Status Key can be specified for each Integration Component so the final data set is the structure of all the keys combined hierarchically. In this case, navigate to Integration Component Key in the explorer under the Service Request Integration Component, create another new record, provide a name, 'StatusKey, set the sequence number to 1 and the key type to 'Status key'. Create a child record in Integration Component Key Fields, provide a name and set the Field Name to 'SR Number'.

Finally, while not absolutely necessary, you should inactivate all fields you are not using for each Integration Component. For an inbound upsert to Siebel, the calling system does not need to provide all the fields that are active in the IO schema, but if the field is active in the IO, then the external system Could send that data element which may have undesired affects depending on the interface. Make sure all fields used in the key are activated as well as all fields being passed from the external system. Unlike BC field lengths, the length property of an Integration Component Field is more important as when an XSD is generated and provided to the external system, this property will frequently be used by the web development tool to validate the data entered into that field. You can also change the XML Tag attribute to a label recognized by the external system (so long as spaces are removed).

One thing to keep in mind is that if an insert is desired, then the calling system should just pass a constant to the user key field, 'Id' so that Siebel will not find a record and a new one will be created. A value like '_New_Record_' is a safe value because the '_' will never be part of a generated row id.

Wednesday, July 21, 2010

About defaults, picks, maps and SetField events

That is an eclectic list of things in the title, and no I do not intend to talk about them all in detail other than to discuss a bit about how they interact and some of the design implications they may cause. So let me start with another list:
  • Pick Maps do not cascade
  • Fields set by a pick map cause a SetFieldValue event
  • Defaults do not cause a SetFieldValue event
  • On Field Update Set BC User Prop will trigger a SetFieldValue event
  • SetFieldValue event triggers a Pick
  • Setting a field to itself does not trigger a SetFieldValue event
So those are the important findings I had to deal with when implementing a seemingly simple requirement. My client had a contact type and sub type. The contact type should be denormalized from the related account's type. Finally, they want to set the contact sub type dynamically to a different value depending on the contact type. By dynamically, I mean not hard coded, so it can be changed without a release.

Let me put all that in functional terms by providing an example. The Account Type has a static LOV with values 'Bank' and 'Government'. The Contact can potentially be created as a child of an account, inheriting information from the account record, and triggering Parent expression default values, or can be created from the Contact screen without an account, but with the option to set the account later. When an account is specified for a contact, the contact type will be set to match the account type, otherwise the contact type should be set to 'Other'. If the Contact type is 'Bank', the contact sub type should get set to 'Retail', and if the contact type is 'Government', the sub type should be set to 'HUD'. So the basic configuration we started with was to put the desired 'dynamic' sub type value in the Low column on the LOV table. Then set up the pick map for contact type as such:

Field Picklist Field
Contact Type Value
Contact Sub Type Low

It would be convenient to just set the pick map similarly on Account Type as:

Field Picklist Field
Account Type Value
Contact Type Value

But the first rule above states this will not work because pick maps do not cascade. This makes some sense as you could conceivably end up with some circular logic. Or in the case where the contact is created as a child of an account, to predefault the Contact Type to the Account Type. But again, according to the rules above, a predefault will not trigger a SetField and hence no pick map.

So in order to trigger the pick map on Contact Type, we need to trigger a SetFieldValue event on this field. What to do. Oh, and I did not want to use script. My solution had a couple of dimensions.
  1. When a contact is created on the Contact Screen and the account is picked, I am going to trigger a set field value on the Account Type by creating a joined field on the Contact BC called Account Type, and add this field to the Account pick map. So this will trigger my SetFieldValue event. I then will add an 'On Field Update Set' BC User property to the Contact BC so that when the joined Account Type field is updated, set the Contact Type to the Account Type. Using a User Property will then trigger the SetFieldValue event on Contact Type which will then trigger the pick map to set the Contact Sub Type. So far so good.
  2. My approach on the scenario when a Contact is created as a child of an Account is not as clean. The problem here is that Predefaults do not trigger SetFieldValue events. And in this case, all the account information will already have been set via Predefault so there is no field being explicitly set by a user to trigger the User property. So I had to get creative. What I did was similar to above but placed identical user properties on Contact First and Last name fields. Since these are required fields that are typically entered first, they will trigger the user properties to set the contact type and sub type. In order to minimize the UI impacts of this admittedly kloogy design, I wanted the visible Contact Type in the applet to default correctly to the Account Type from the parent record. This means that when the User sets the First Name (or the Last) the Contact Type will already have the correct value so the User Property would essentially set it to itself. The last rule above states this will not trigger the SetFieldValue event. To get around this I create two User Properties in sequence, the first to set the Contact Type to null, and the second to set it back to the Account Type. Because I am putting the properties on both the First and Last name (to accommodate different user's field population sequences), I also want to add a conditional to the user properties to not execute if the Sub Type has already been set.
What does all this leave us with? In addition to the pick map on the Account field mentioned first, here are the On Field Update Set user properties on the Contact BC:
  1. "Account Type", "Contact Type", "[Account Type]"
  2. "First Name", "Contact Type", "", "[Contact Sub Type] IS NULL"
  3. "First Name", "Contact Type", "[Account Type]", "[Contact Sub Type] IS NULL"
  4. "Last Name", "Contact Type", "", "[Contact Sub Type] IS NULL"
  5. "Last Name", "Contact Type", "[Account Type]", "[Contact Sub Type] IS NULL"
I am going to leave it there, but this actually gets even more complicated. Because a contact can be created from a pick applet from a service request, I also had to account for predefaulting the account to the SR's account and this impact this would have on predefaulting Contact Type and Sub Type. If anyone would like to see how this is done, here is where to start.

Tuesday, June 15, 2010

Why can't I see that View???

There are times when troubleshooting an issue, that you start searching Oracle support and you find a ticket that has your problem, but the solution does not apply, and then you keep looking and you find another one that looked in a different direction yet still, the solution did not apply. And you think, I wish there was a single place that listed all the reasons that this could happen. Siebel does do this sometimes a handful of their generic error messages, listing ten different reasons you could be getting that message. Well I am going to apply that format to some basic configuration items.

The first one is View Visibility. So without further ado, here is a list of reasons you may not see a particular view in the GUI (Please feel free to add additional reasons in comments):
  1. The basics: View has been created in Tools, compiled into the SRF, and the GUI you are looking at matches the SRF you compiled the view into. I know this part should be obvious, but we are aiming at completeness
  2. The View needs to be added to a screen. Make sure the View attribute spelling matches the spelling of the View object. Check the Display In Page unless you want this to be hidden, and probably the Display In Site Map. These default to True.
  3. The View exists in the GUI meta data. Administration - Application -> Views. Again make sure the spelling matches the repository object
  4. The View has been added to a responsibility that your user login has access to
  5. The Responsibility Cache has been cleared. Doh!
  6. Log Out and Log Back In (Views are not immediately visible in the session where it was added)

Ok those are the basics. Now for the advanced:

  1. Navigate to the Application - Administration -> Responsibilities -> Tab Layout view. Check the responsibility which is primary for your user login in the top applet. Query for the Application you are logged into in the middle applet. Find the Screen you have placed the view under in the third applet, and in the fourth applet, insure the Hide flag is not checked. This is more applicable for trying to figure out why a vanilla view is not exposed
  2. Navigate to the Application - Personalization -> Views view. Query for the view which is you are having issues with. Review the Condition Expression. This expression should either be null, or should evaluate to true for the logged in user. The date range should either be null or should include today's date in its range.
  3. Last but not least, it may be a license key issue. Siebel implement license keys through their views. One way to test whether this is the issue*** is to copy the view, add the copied view to a responsibility, add the copied view to the View admin and to a responsibility and clear the cache, log out and back in. If you can see the view now, then you need to get the license key. Not all license keys indicate additional purchases. There are a couple of instances I have found where a view just dropped out of the standard set as a defect and the fix was to provide a license key to get it back (Remote System Preferences view is an example I ran into in 8.o)

Some Additional Pointers:

  • Always copy and paste view names between objects or between Tools and the UI to avoid spelling errors, as they are one of the most common problems in this area.
  • Avoid the use of apostrophes to indicate possesive as this will often cause issues down the road. (I know siebel has some vanilla instances where theu use it with ...Manager's... but trust me, avoid this).
  • When copying views, insure the Thread and Visibility applets are actually present as View Web Template Items for that view. No error is thown to the UI if they do not match, but buried in the Siebel.log, you will find them. They manifest by not executing the correct search when navigating across views. For instance if you are on an All view and navigate to the correlated My view, but one that has an invalid applet, the view will appear to change in the UI, but the data does not, so the My view will show All view data.
*** This should be done just to test that the license key is the issue. A copied view really should not go into production as this is a slippery slope which is really not a good idea in the long term (and its against the license agreement too)

UPDATE: Dos, in comments, points out a better way to see if Licensing (or some other problem) is at issue. Just paste the following into your URL after the start.swe? replacing the view name in bold with the view you are having problems with:

SWECmd=GotoView&SWEView=Quote+List+View

You need to replace a space with a +. Since 'Quote List View' is part of the Orders module requiring a specific license key, you get the following message if you do not have the key:

View 'Quote List View' is not licensed for this site.(SBL-DAT-00327)

Tuesday, May 4, 2010

Making an MVF Required

If you were to do a google search for this requirement you would probably find a lot of posts returned so I do not want to claim I am doing anything new. In fact, My Oracle Support even has a post about doing this declaratively and they claim this has been added to bookshelf for 8.x as the documentation defect is shown to be fixed but I cannot find the relevant page. (Search for Document ID ID 476071.1). Anyway I just wanted to put all this information in one place for my own reference and for anyone else that finds this useful.

First, the configuration:

The key thing here is that since the calculated field name has the same name as the MVF with just a space added, the message displayed to the user will be the same as well. Next, the appearance:

To make the red asterisk appear, add the following text to the Applet Control Caption - String Override whereever in the string you need the asterisk:

<img src="images/icon_req.gif" alt="Code Validated field" border="0" space="0" hspace="0">

Keep in mind Siebel automatically adds the colon (:) after the field label so that in Siebel 8.x, the red asterisk will appear before the colon while other required fields will have the asterisk appear after the colon. If you wish to be even more precise, and don't we all, you can remove the field label Siebel creates for you all together and create your own so you can place the colon where you wish. This is described in Document 852952.1 on My Oracle Support in a pretty superfluosly wordy way, so here is the excerpted nickel version:

  1. First remove the existing Field Label of HTML type Text.
  2. Drag a drop a Control of Type Label in the Grid Layout Applet web template
  3. Set the Caption for this Label Control. To show for example,

Last Name: *

Enter the following in the Caption - String Override:

Last Name:<img src="images/icon_req.gif" alt="Code Validated field" border="0" space="0" hspace="0">

Set the property HTML Type = Label


As for making the asterisk conditionally appear (for instance to support the BC level functionality enforced by the calculated field in the Required Field User Property), there is not really a way to do that, but a close approximation if you only have one is to use toggle applets which toggle on the same conditional value that the fields user property is based on.

Wednesday, April 28, 2010

Modifying the Personalization Profile

You may have found Profile Attributes to be a useful addition in Siebel 7 as a better way to use global variables. The most common use of profile attributes is exactly that. You use the script expression:
TheApplication().SetProfileAttr("CurrentLogLevel", 5);
This variable can then be referenced elsewhere in script:
var iLogLevel = TheApplication().GetProfileAttr("CurrentLogLevel");
Alternatively, you can get this value in a calculated field using just:
GetProfileAttr("CurrentLogLevel")
OK, so that is pretty elementary as it is documented pretty well in bookshelf. This assumes a relatively dynamic value of the global variable, one that is determined programatically during a user session, and whose value is lost when the session ends. But what if you want to reference a user attribute that persists. There are already a couple of specialized functions that reference some user attributes:
PositionName()
LoginName()
LoginId()
PositionId()
But what if you want to add your own? So here is my requirement. I want to store a custom logging level attribute that can be set on a user by user basis. I will go into more detail in future posts about what I might want to do with this value. So the first thing to do is to either find a place to put this value or to extend a table to make a place. I prefer the latter, so I am going to add a new number column, X_LOG_LEVEL to the S_USER table:
This field should then be exposed in the Employee BC (it could alternatively be exposed in the User BC if an eService or eSales application was in play but I am going to keep this simple for now). Create a new single value field to expose this column (I added some validation too):
Now, in order to actually use this value, I will need to expose it in the GUI. So lets put it in the Employee List applet so it will be visible in the Administration - User -> Employees view.
Once the list column exists, edit the web layout and add this control to the list in the Edit List mode. Next, I am going to expose this field in a very special BC, Personalization Profile. This BC is instantiated when the user logs in and its fields basically represent all the potential attributes of the logged in user including the User, Employee, Contact, Position, Division, and Organization. As I will show in a minute, its fields are referenceable as profile attributes. There is already a join in this BC to the S_USER table so just create a new SVF to expose this column.Finally lets add some script to the Application Start event for the application you are using. What I want to do here is set a global variable type profile attribute as I described in the beginning of this post called CurrentLogLevel to the value on the User record (which I get from the Personalization Profile) if one was set, otherwise to set it to a constant value. Then if the logging level is sufficiently high, start application tracing and push a line to that new file:
var sLogLevel = 3;
if (TheApplication().GetProfileAttr("User Log Level") != "")
TheApplication().SetProfileAttr("CurrentLogLevel",
TheApplication().GetProfileAttr("User Log Level"));
else TheApplication().SetProfileAttr("CurrentLogLevel", sLogLevel);
if (TheApplication().GetProfileAttr("CurrentLogLevel") > 4) {
TheApplication().TraceOn("Trace-"+TheApplication().LoginName()+".txt", "Allocation", "All");
TheApplication().Trace("Application Started");
}
Ok. Now compile everything. The base case is with no user log level set. When you open this application, the application start event will trigger and since I have not done anything yet with my user log level, it will default to 3 and no trace file will be created. You'll have to trust me so far. Next navigate to Administration - User -> Employees, and query for your login. Right click to show the columns, and move User Log Level to the Selected Columns list. Now set this field value to 5. Log out, and log back in. You will now find a file in you \BIN directory called Trace-SADMIN.txt (I obviously logged in as SADMIN but the filename is dynamic as well) with the line:
Application Started.
Done. I have used this for a log attribute I will talk about more in future posts but you can use this feature to store any type of data on a table linked to the logged in user. This is sometimes useful for referencing functional information relating to certain business processes. You could use an attribute from this BC in a calculated field on a different BC to determine whether a record on that BC should be read only for instance. Good luck

Tuesday, April 20, 2010

How to call Workflow - Custom Remote

My previous posts discussed some of the mechanisms to call a workflow process along with their pros and cons. I alluded to how the mode that was desired would determine which business service to call. Workflow Process Manager is the vanilla business process which allows for executing a workflow process in Local Synchronous mode. Siebel provides another business service, Server Requests, which is capable of starting a task in a different component, hence running the process remotely. Unfortuneately, the necessary inputs to this business service must be passed in a child property set, so this business service cannot be called directly from the action of an action set using the business service context attribute. What needs to be done is to is to call a custom shell business service which fills a child property set with the correct attributes, then calls the vanilla Server Request business service.

Let's see how this is done. First of all, a new custom business service needs to be created:

Now create a new custom method for this service:

Finally, create the following method arguments for this new method:

Ok. Now for the script. The script I will about to specify has several features:

  • Calls the Server Requests business service which runs a workflow process in the Workflow Process Manager server somponent in its own thread
  • Input Properties to the Server Requests business service cannot be passed through the business service context attribute of an Action, as Siebel expects these properties as a child property set.
  • Can be called from a Run Time Event-Action by setting all properties as Profile Attributes
  • Can be called from a business service or workflow process by setting all properties directly as Input properties
  • Takes input properties, either from from profile attributes or from input property set, and passes them as custom input properties to a workflow process.
  • Profile Attribute name will be "PassThrough1", for instance, and the profile attribute value must be in the format "", ""
  • Can optionally specify a specific application server to run on, otherwise will allow the load balancer to assign the server
  • By default, will run in Remote Synchronous mode if user is connected, otherwise will run Remote Asynchronously.
  • Can optionally be set to be run as explicitly Remote Asynchronously

This is written in eScript and there are plenty of different ways to go about this, but this is a rough guide (actually, not so rough):

Thursday, April 15, 2010

Migrating Meta Data - The Release field

Something I have now implemented on a couple of clients and I find quite useful is an enhancement to ADM (Application Deplyment Manager) to group meta data items together for the purpose of a release. Out of the box, ADM has data types for migrating LOVs, Views, Assignment Rules, etc from a source environment to a target environment. The mechanism provides for a search specification to determine which records of that data type should be migrated. This search specification can get quite complicated over time though.

What I propose is to extend the base record of each meta data object with a new column signifying the release. Simply add the column X_RELEASE to the parent table (S_ASGN_GRP for Assignment Rules for instance). Expose this column in the business component the table is based on, and in the applet the BC is based on. I recommend a varchar data type which will provide a little flexibility in how you version your releases. Finally, modify the ADM integration object. These are the ones prefixed with 'UDA'. You will need to add the same Release field (corresponding to the BC field name) to the Integration Component corresponding to the BC the field was added to.

Now, in the GUI you can mark all the record of that meta data object with the release description, such as '1.0'. In the Deployment Filter of the Deployment Project/Session, you can then specify, [Release] = '1.0'.

Thursday, April 8, 2010

Predefaulting Joined Fields on Created By

My client had a requirement to expose the Created By user in the list applet but instead of just showing the row id of the user record, or even the user login, they wanted a more intuitive expression, which I suggested to be [First Name] + ' ' + [Last Name] + ' (' + [Login] + ')'. This works great for them. The only problem is that the field would not predefault correctly when the record was first created. It would only look right after a refresh or requery of the BC. I did a search of metalink and came up with SR 38-1137205351 which basically said what I wanted could not be done because the join to S_CONTACT to get the first and last name would not be done until a requery and there is no system function (like LoginId() or PositionName() ) for the creators full name.

But then I realized I could use a Profile Attribute from the 'Personalization Profile' BC. This BC if you are not familiar with it contains a collection of attributes about the logged in user and all the entities linked to that user (position, organization, contact, division, etc). You can even customize this BC to add your own custom attributes that can be referenced from anywhere. That was not necessary for what I needed though. The attributes in this BC can be referenced using a simple GetProfileAttr statement which can be used in an expression for calculated fields and pre/post default expressions. So here is what I ended up with:



A predefault expression of -> Expr: 'GetProfileAttr(First Name)'. The Created By Display field is what is exposed in the GUI. In addition a pick map will be necessary on this field in the BC:








Tuesday, March 30, 2010

Type Specs and Predefaults

There are many times when you need to constrain an applet by type. I will explain a declarative technique for doing so in three general situations. In all of these cases, we want to constrain the applet to only show records of a particular type and we want new records to predefault to that type.

Single Valued - Pick Applet: Lets say you have two different type of contacts, 'X Type' and 'Y Type'. I want to be able to pick the 'X Type' contact for an account and the 'Y Type' contact for an account into two different account fields (one for each). I will need to create two new pick lists:

Name: PickList X Type Contact
Business Component: Contact
Long List: Y
Bounded: Y
Type Field: Type
Type Value: X Type

Name: PickList Y Type Contact
Business Component: Contact
Long List: Y
Bounded: Y
Type Field: Type
Type Value: Y Type

On the Account, for the field 'X Contact' set the picklist to 'PickList X Type Contact', set the pick map appropriately, expose a contact pick applet on the account field, etc., and do the same for the account field 'Y Contact'. When the pick applet opens, it will only show Contacts with the respective type field and new contacts created within this pick applet will have there type predefaulted. This is the only way you can declaratively get a predefault of different types. Picklist objects are very "thin" so having a lot of them with different type specs or search specs does not really result in a lot of maintenance overhead. You can even use the same pick applet for both fields.

Multi Valued - MVG/Associate Applet: When the Contacts you wish to add to your hypothetical account are multi valued instead of single valued, a pick list will not help you. Instead you need to use the Type Field/Value on the Multi Value Link. So on the Account, let say you have a Contact MV Link called 'Z Contacts'. Set the properties thus:

Type Field: Type
Type Value: Z Type

This will predefault new contact created within the Associate applet to 'Z Type'. You will also need to add a Search Spec to the Associate applet to constrain the records to 'Z Type'. It is important to do both in this scenario as the MV Link Type spec alone will not update the type of existing records.

Standard List/Form Applet: When constraining and predefaulting a value on a regular applet as opposed to a popup applet, you need to leverage Aspect user properties. These were new properties documented after 7.7 I think and bookshelf does a decent job of documenting them. Basically you need to do the following for each applet representing each different type respectively:
  • Applet class is either CSSFrameListBase or CSSFrameBase
  • Create an Applet User Property:
    Name: Default Aspect
    Value: X Type
  • Create a Field user property on the Contact BC, Type field:
    Name: Aspect Default Value: X Type
    Value: X Type
The Default Aspect property value must match the Field Aspect Default Value expression after the colon (in this case X Type). This confiuration will predefault new records to be a particular type but will not constrain the records that appear. If you need to constrain the records as well, you can add a standard search spec either to the applet or if it is a child applet, to the link.

Monday, March 29, 2010

Dynamic Drilldowns - No Default Drilldown

When configuring a dynamic drilldown, there is a Siebel limitation that the destination view on the default drilldown object be accessible. So if the else condition occurs, or if the default drilldown object is explicitly referenced by the dynamic drilldown destination, then the view used in the drilldown object must exist in the current user's responsibility.

But what if you don't want anything to happen when the else condition occurs, or when Siebel reverts to the default condition. In this case you can specify the view housing the applet you are working on, without specifying any sourcec/destination fields or BC. This way when the default condition occurs, the system drills you down to the same view, but because no source/destination is set, no additional query is executed. WHat this looks like visually in the UI is a blue hyperlink field that when clicked, nothing happens.

Let's see how this is configured. In this scenario, we will need three Drilldown Object records:

Name: Account
Hyperlink Field: Account
View: Contact List View

Name: Custom Type X
View: Custom Account Detail View X
Source Field: Account Id
Business Component: Account

Name: Custom Type Y
View: Custom Account Detail View Y
Source Field: Account Id
Business Component: Account

For the first Drilldown Object record, Account, Navigate to Dynamic Drilldown Destinations, and create the following records:

Name: Custom Type X
Field: Contact Type
Value: X
Destination Drilldown Object: Custom Type X

Name: Custom Type Y
Field: Contact Type
Value: Y
Destination Drilldown Object: Custom Type Y

This configuration will navigate the user to the view 'Custom Account Detail View Y' if the field 'Contact Type' = 'Y' and to the view 'Custom Account Detail View X' if the field 'Contact Type' = 'X', otherwise the drilldown will do nothing.

NOTE: One think to keep in mind when doing this is that since the default link is meant to be self-referential, this only makes sense if the applet is leveraged in a single view. If the applet is used in multiple views, then the default drilldown will actually navigate you away from the view you were on.

Dynamic Drilldowns - Theory and Instructions

After doing a search on supportweb to help a coworker today, I realized the documentation on this functionality was especially... unclear. So let me try to make it more so.

This is an area that changed between siebel 2k and 7 and I actually think the 2k configuration steps were more clear. Anyhow, the order of operations Siebel uses to determine the dynamic drilldown is
  1. Lookup the Drilldown object by Hyperlink Field (There should only be one for any field)
  2. Resolve the dynamic drilldown object Field / Value pairs to determine the correct Destination Drilldown Object
  3. Use the correct Destination Drilldown Object to determine the View and source/destination fields/BC properties from the linked Drilldown Object record.
  4. Navigate to the view
One way to think of the Dynamic drilldown object in Tools is like an If/Else statement. Siebel checks the conditions of the field / value pairs, and navigates to that drilldown object if a match is found. If no condition matches, the else, then the destination view of the current Drilldown Object record is used. This implies that Only the Drilldown Object with the Hyperlink field specified should have dynamic drilldown object records. The other drilldown object records representing different view destination views should not have dynamic drilldown object records configured. It is also not strictly necessary that there be a Dynamic Drilldown Object condition that explicitly specifies it's parent Drilldown Object as the Parent Drilldown Object will always satisfy the else anyway. If for clarity's sake it is considered desireable to see this configuration though, it does not hurt.

Here is an example for a dynamic drilldown on a contact list applet that drills down to two different account views based on a contact type field.

Navigate to Applet, Drilldown Object, Create the following records:

Name: Account
Hyperlink Field: Account
View: Custom Account Detail View X
Source Field: Account Id
Business Component: Account

Name: Custom Type Y
View: Custom Account Detail View Y
Source Field: Account Id
Business Component: Account

For the first Drilldown Object record, Account, Navigate to Dynamic Drilldown Destinations, and create the following record:

Name: Custom Type Y
Field: Contact Type
Value: Y
Destination Drilldown Object: Custom Type Y

This configuration will navigate the user to the view 'Custom Account Detail View Y' if the field 'Contact Type' = 'Y' and to the view 'Custom Account Detail View X' if the field 'Contact Type' is anything else, including the value 'X'.

Wednesday, March 17, 2010

Using a Sequence

Sequences have a lot of use in added logical order to a complex structure. Typically you have a master/parent record at the top of a view, and you need to have numbered child records. Each parent record would have a unique set of numbered children. Sequences can also be used for version control. Here are the steps to making a sequence BC:

Create a BC of class CSSSequence whose name is a concatenation of the source BC name plus '.Version (Sequence)' where Version is the name of the source BC field holding the sequence value. So for example, 'Quote.Version (Sequence)' would be a BC name. In addition set the Sort Spec to Sequence (DESCENDING) and the Table to the same table as the source BC. Create he following fields in the new sequence BC:

Name = Sequence
Column =
Type = DTYPE_INTEGER

Also create fields for the logical key field making the sequence unique. Usually Name or Id or some unique Number. This is how the system finds the next sequence: looks at the source BC with this field value, finds the maximum sequence value and adds one.

Create a link between the Base BC and the Sequence BC, with the Source Field and Destination fields holding the same value. Again this is the logical key indicating when the sequence should reset. Typically Name or Id or a unique Number.

Add the new sequence BC to the Business Object where the sequence will be used linked by the newly created link.

Customized Version Control - Binary

As of at least 7.8 you can now implement your own forms of version control for custom objects. Bookshelf provides some specifications for BC User properties in the Developer's Reference, User Properties, Business Component User Properties:

Revision Condition
Revision Copy
Revision Field

Using these with the Vanilla method, Revise, allows you to achieve funcitonality similar to what you see in a quote. To implement, create a custom minibutton on an applet and set the method to be 'Revise'.

On the BC the applet is based on, create a User Property

name = Revision Field
value = Version

Where version is the name of a BC field storing a sequence. Create a sequence BC and link for the source BC.

Revision Condition is an optional user propery that allows to to disable the Revise button under certain conditions. Here is an example if you only wanted the button enabled when the Status = 'Active'. You can have as many of these as you like and the system will interpret an AND condition between each of them.

Name = Revision Condition 1
Value = "Status", "Active"

Pressing the Revise button will make the current record Read Only, and create an editable copy. The copy is immediately 'Active' and the revised original is immediately old. This functionality can be combined with the user property Active Field to make control the editability of these records. To do that, create or identify a flag field on the BC; I'll use Edit Flag as my example. Create a new BC user property:

Name = Active Field
Value = Edit Flag

Now when you create the revision, the new Active copy is editable and the old copy is read only, or archived.

This is a binary way to look at archiving which may be useful in many instances (Quotes obviously), but there are other times when a tertiary state is needed. That is you may want to tweek the settings of the new version before activating it, leaving the previous version active until ready to activate the revision. That will be the subject of another post.