I have a love/hate relationship with Lawson comments. They are an awesome feature, but implemented in such a way that you’d think somebody made it hard on purpose. This post is going to be about how to deal with comments (mostly in large quantities). This is everything that you “need” to know, not everything you “want” to know. Probably once a week, I get asked “Can you query for comments?”. The answer is Yes*. The other question I get a lot is: “Can you upload comments?”. Again, the answer is Yes**.
*No, you cannot query them out through DME.
**No, you cannot use MS Add-ins.
Uploading Comments
Let’s first talk about adding comments to Lawson so that we have something to query out. There is only one way to add comments to Lawson, and that’s through the writeattach.exe cgi program. How you choose to implement it is up to you, but I’m going to explain how to do it through Lawson ProcessFlow. You can add comments via either GET or POST, but I prefer the POST because I have more control over how the comments get added and I don’t have to do a lot of encoding work.
The basic XML for the comments looks like:
<ATTACHXML>
<_AESC>IE</_AESC>
<_ANAM>Comment Title</_ANAM>
<_ATXT>
<![CDATA[
Comment Text is here
]]>
</_ATXT>
<_OUT>XML</_OUT>
<_PDL>PRODUCTLINE</_PDL>
<_FN>FILENAME</_FN>
<_IN>INDEXNAME</_IN>
<K1>KEY1VALUE</K1>
<K2>KEY2VALUE</K2>
<K3>KEY3VALUE</K3>
<K4 />
<_ATYP>C</_ATYP>
<_AUDT>COMMENTTYPE</_AUDT>
<_USCH />
<_DATA>TRUE</_DATA>
<_ATTR>TRUE</_ATTR>
<_AOBJ>TRUE</_AOBJ>
<_OPM>M</_OPM>
<_ECODE>FALSE</_ECODE>
</ATTACHXML>
The URL for my WebRun node is: cgi-lawson/writeattach.exe and I send the XML above as a POST string. In order to figure out what to put in the tags, I used dbdef. In order to add comments you must follow these steps. First, verify that the file allows attachments. Second, find the index name and what fields go in the index value fields (you can add more as needed – but the values should be the actual value like 0001 instead of 1). The only thing that I don’t have a good way to get is the value for is the _AUDT tag. This is the comment type, so for forms that allow more than one comment type, this value will change. I’m sure there’s some method to figure this out, I just don’t know what it is. I generally use Fiddler, add a comment, then check the calls.
Note: If you do use process flow, you have to be careful with the CDATA nodes. Process flow will interpret the <! as the beginning of a process flow variable and try to replace it. As a result, if you need the CDATA nodes, you should build the post string either in the XML node or an Assign node and then put that variable name in the WebRun.
Querying comments
There are two ways to query comments. One is using SQL (my personal preference, but not always applicable) and the other is using cgi calls.
For the SQL version, it’s important to talk about the table structure used to actually store the data. For each table that allows attachments/comments, there are two tables to store the comments, a “Header” and a “Detail”. The “Header” contains the name of the comment as well as the start of the comment. The “Detail” table contains the rest of the comment.
For a table that allows comments, the comment tables are always named “L_<Table_Type><Table_Prefix>”. Consequently, comments for the APINVOICE table (prefix is API) would be named L_HAPI and L_DAPI. The H and the D indicate Header and detail.
The relationship between the base table (APINVOICE) and the Header comment table (L_HAPI) is:
APINVOICE.L_INDEX = L_HAPI.L_INDEX
'APINVOICE' = L_HAPI.TABLE_NAME
The relationship between the Header (L_HAPI) and detail comment table (L_DAPI) is:
L_HAPI.L_INDEX = L_DAPI.L_INDEX
L_HAPI.ATCHNBR = L_DAPI.ATCHNBR
The data relationships are:
APINVOICE 1:M L_HAPI 1:M L_DAPI
This gets us data that looks something like this (I’m not displaying duplicated data as the query results would actually be; this is to aid understanding of the relationships):
Invoice |
L_INDEX |
ATCHNBR |
Comment Name |
Seq |
Comment |
122344 |
zzzz |
1 |
Comment 1 |
1 |
some comment detail |
|
|
|
|
2 |
More comment detail |
|
|
2 |
Comment 2 |
1 |
some comment detail |
The OBJECT field of the Header and Detail tables contains the comments. For the Header table, the OBJECT field actually contains more than the comment. Technically, it’s comma-delimited and each of the first three “field”s has it’s name as part of it. The fourth field is the beginning of comment itself. However, the data is clearly space padded, so I would strongly urge you to delimit based on places, not on commas, if for not other reason than your comment could contain commas and then you’re in trouble. If you’re wondering, the actual comment data begins at character 96. The “Detail” table has a record for any comment that is longer than 416 characters. Each Detail OBJECT field is 1024 characters, so for each comment that is longer than that, there will be an additional record. You must put all of these records back together to form the full comment.
Here is what a query for invoice comments might look like (Oracle Version).
SELECT API.COMPANY, API.VENDOR, API.INVOICE, H.L_INDEX, H.ATCHNBR, H.R_NAME, H.OBJECT, D.SEQNBR, D.OBJECT
FROM LAWSON.APINVOICE API
LEFT OUTER JOIN LAWSON.L_HAPI H
ON API.L_INDEX = H.L_INDEX
AND H.FILENAME = 'APINVOICE'
LEFT OUTER JOIN LAWSON.L_DAPI D
ON H.L_INDEX = D.L_INDEX
AND H.ATCHNBR = D.ATCHNBR
WHERE API.INVOICE = '123456'
ORDER BY D.SEQNBR
You should always make sure that you OUTER JOIN your comment tables to your base tables as the comment tables are never required. Because you may return multiple detail records for each base record, you will have to deal with “putting them back together”. My personal preference is to use the Oracle Hierarchy queries (which I posted on here). However, I have been known to use LEAD and LAG when I have a clearly defined recordset. If you are using Crystal, you can use the Hierarchal functions there as well.
Non-SQL Version
Now that you’ve seen what it takes to create a comment and what the data looks like in the tables, it’s time to discuss the non-SQL ways to get the data. There are two options, ListAttachments and getattachrec.exe programs.
The shortcut method (version 9.0.1) is to use ListAttachment:
/lawson-ios/action/ListAttachments?&dataArea=<PRODUCTLINE>&fileName=<TABLENAME>&indexName=<INDEX>&K1=<Key1Value>&K2=<Key2Value>&K3=<Key3Value>&K4=<Key4Value>&K5=<Key5Value>&outType=XML
You follow the same basic rules to build this URL as you would to build the writeattach.exe. The primary difference is that you do not need to give explicit values (like 0001 for company).
A response from the APINVOICE comments might look like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<ACTION actionName="LISTATTACHMENTS">
<LIST numRecords="1" maxRecordCount="500" hasMoreRecords="false" status="pass">
<MSG />
<ATTACHMENT attachmentNbr="zz" indexName="APISET1" attachmentSize="680" createDate="20100112" modifiedDate="20100112" createUser="lawsonuser" modifiedUser="lawsonuser" dataArea="PROD" K5="9999" K4="0" K3="3694823" K2="20840" K1="120" createTime="074725" modifiedTime="074725" attachmentCategory="commentAtch" attachmentType="A" lIndex="yEXb" fileName="APINVOICE" status="pass">
<MSG />
<ATTACHMENT-NAME>
<![CDATA[ Attachment name]]>
</ATTACHMENT-NAME>
<ATTACHMENT-TEXT>
<![CDATA[ Attachment data ]]>
</ATTACHMENT-TEXT>
</ATTACHMENT>
</LIST>
</ACTION>
As for the getattachrec.exe, I don’t have a reliable method of building these, it’s tedious no matter how you do it. Once you figure out how to get your comment (like the Invoice example above), you might be able to replicate it without having to go through the entire process every time, but you’ll have to use trial and error. The biggest issue you’ll face is that some tables allow for multiple comment types, and you won’t know which ones (if any) exist.
My preferred method to build the URLs the first time around is to either put Lawson in debug mode or use Fiddler. There are three main calls that you will need to focus on.
1) Drill from a Lawson screen on our record (like AP90.1)
2) Based on the data from #1, construct our next URL to get the comment header
3) Based on the data from #2, construct our next URL to get the comment detail
1) Here’s an example of the IDA (Drill) URL. This is for the same invoice as above.
/servlet/Router/Drill/Erp?_OUT=XML&keyUsage=PARAM&_TYP=CMT&_PDL=PROD&_SYS=AP&_TKN=AP90.1&_KNB=50&01=120&08=CHPV&AVi=%20%20%20%2020840&50=3694823&AQ=9999&03=%20%20%20%2020840&_LKN=50&_RECSTOGET=0
This gets us output like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<IDARETURN productline="PROD" title="">
<COLUMNS />
<FINDNEXT />
<PREVPAGE />
<NEXTPAGE />
<BASEURL />
<LASTUSED />
<LINES count="4">
<LINE>
<IDACALL type="CMT">
<![CDATA[ cgi-lawson/getattachrec.exe?_AUDT=A&_IN=APISET1&K1=120&K2=20840&_FN=APINVOICE&K3=3694823&K4=0&_ATYP=C&K5=9999&_TYP=CMT&_OPM=C&_OUT=XML&_ATTR=TRUE&_DRIL=TRUE&_AOBJ=TRUE&_PDL=PROD&_ON=Invoice+Note%2FReport%2FCheck+Comments ]]>
</IDACALL>
<ATTACHMENTCALL type="CMT">
<![CDATA[ lawson-ios/action/ListAttachments?attachmentType=A&indexName=APISET1&K1=120&K2=20840&fileName=APINVOICE&K3=3694823&K4=0&attachmentCategory=C&K5=9999&drillType=CMT&outType=XML&dataArea=PROD&objName=Invoice+Note%2FReport%2FCheck+Comments ]]>
</ATTACHMENTCALL>
<COLS>
<COL>
<![CDATA[ Invoice Note/Report/Check Comments ]]>
</COL>
</COLS>
<KEYFLDS />
<REQFLDS />
</LINE>
<LINE>
<IDACALL type="CMT">
<![CDATA[ cgi-lawson/getattachrec.exe?_AUDT=N&_IN=APISET1&K1=120&K2=20840&_FN=APINVOICE&K3=3694823&K4=0&_ATYP=C&K5=9999&_TYP=CMT&_OPM=C&_OUT=XML&_ATTR=TRUE&_DRIL=TRUE&_AOBJ=TRUE&_PDL=PROD&_ON=Invoice+Notes ]]>
</IDACALL>
<COLS>
<COL>
<![CDATA[ Invoice Notes ]]>
</COL>
</COLS>
<KEYFLDS />
<REQFLDS />
</LINE>
<LINE>
<IDACALL type="CMT">
<![CDATA[ cgi-lawson/getattachrec.exe?_AUDT=D&_IN=APISET1&K1=120&K2=20840&_FN=APINVOICE&K3=3694823&K4=0&_ATYP=C&K5=9999&_TYP=CMT&_OPM=C&_OUT=XML&_ATTR=TRUE&_DRIL=TRUE&_AOBJ=TRUE&_PDL=PROD&_ON=Invoice+Report+Comments ]]>
</IDACALL>
<COLS>
<COL>
<![CDATA[ Invoice Report Comments ]]>
</COL>
</COLS>
<KEYFLDS />
<REQFLDS />
</LINE>
<LINE>
<IDACALL type="CMT">
<![CDATA[ cgi-lawson/getattachrec.exe?_AUDT=C&_IN=APISET1&K1=120&K2=20840&_FN=APINVOICE&K3=3694823&K4=0&_ATYP=C&K5=9999&_TYP=CMT&_OPM=C&_OUT=XML&_ATTR=TRUE&_DRIL=TRUE&_AOBJ=TRUE&_PDL=PROD&_ON=Invoice+Check+Comments ]]>
</IDACALL>
<COLS>
<COL>
<![CDATA[ Invoice Check Comments ]]>
</COL>
</COLS>
<KEYFLDS />
<REQFLDS />
</LINE>
</LINES>
</IDARETURN>
2) Run each URL that you find in the IDACALL nodes to try and get a comment header. Running one of these URL’s (the Invoice Note/Report/Check Comments one) will give us something like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<Report cgidir="/cgi-lawson/" executable="getattachrec.exe" productline="PROD" filename="APINVOICE" token="Token" keynbr="KeyNbr">
<QueryBase exepath="/cgi-lawson/writeattach.exe">
<![CDATA[ _OUT=XML&_PDL=PROD&_FN=APINVOICE&_IN=APISET1& ]]>
</QueryBase>
<WinTitle>
<![CDATA[ Invoice Note/Report/Check Comments ]]>
</WinTitle>
<DrillAround>
<DrillData>
<DrillType>Comment</DrillType>
<DrillName>
<![CDATA[ Invoice Note/Report/Check Comments ]]>
</DrillName>
<DrillUserType>A</DrillUserType>
<DrillScheme>
<![CDATA[ none ]]>
</DrillScheme>
<OneOnly>F</OneOnly>
<NoDetail>F</NoDetail>
<NoDrill>F</NoDrill>
<DisplayOrder>A</DisplayOrder>
</DrillData>
<DrillData>
<DrillType>Comment</DrillType>
<DrillName>
<![CDATA[ Invoice Notes ]]>
</DrillName>
<DrillUserType>N</DrillUserType>
<DrillScheme>
<![CDATA[ none ]]>
</DrillScheme>
<OneOnly>F</OneOnly>
<NoDetail>F</NoDetail>
<NoDrill>F</NoDrill>
<DisplayOrder>A</DisplayOrder>
</DrillData>
<DrillData>
<DrillType>Comment</DrillType>
<DrillName>
<![CDATA[ Invoice Report Comments ]]>
</DrillName>
<DrillUserType>D</DrillUserType>
<DrillScheme>
<![CDATA[ none ]]>
</DrillScheme>
<OneOnly>F</OneOnly>
<NoDetail>F</NoDetail>
<NoDrill>F</NoDrill>
<DisplayOrder>A</DisplayOrder>
</DrillData>
<DrillData>
<DrillType>Comment</DrillType>
<DrillName>
<![CDATA[ Invoice Check Comments ]]>
</DrillName>
<DrillUserType>C</DrillUserType>
<DrillScheme>
<![CDATA[ none ]]>
</DrillScheme>
<OneOnly>F</OneOnly>
<NoDetail>F</NoDetail>
<NoDrill>F</NoDrill>
<DisplayOrder>A</DisplayOrder>
</DrillData>
</DrillAround>
<ParentRec>
<QueryVal>
<![CDATA[ _AK=yEXb ]]>
</QueryVal>
<RecAtt Action="Add">
<AttType>C</AttType>
<UsrType>A</UsrType>
<AttName>
<![CDATA[ Add Comment ]]>
</AttName>
<QueryVal>
<![CDATA[ K1=0120&K2=++++20840&K3=3694823&K4=000&K5=9999&_ATYP=C&_AUDT=A&_USCH=none&_DATA=TRUE&_OPM=M& ]]>
</QueryVal>
</RecAtt>
<RecAtt Action="">
<CrtDate>
<![CDATA[ 01/12/2010 ]]>
</CrtDate>
<CrtTime>
<![CDATA[ 07:47:25 ]]>
</CrtTime>
<ModDate>
<![CDATA[ 01/12/2010 ]]>
</ModDate>
<ModTime>
<![CDATA[ 07:47:25 ]]>
</ModTime>
<AttSize>619</AttSize>
<HdrSize>95</HdrSize>
<AttName>
<![CDATA[ Comment Name ]]>
</AttName>
<AttType>C</AttType>
<UsrType>A</UsrType>
<AttCreator>lawsonuser</AttCreator>
<AttModifier>lawsonuser</AttModifier>
<QueryVal>
<![CDATA[ K1=0120&K2=++++20840&K3=3694823&K4=000&K5=9999&_ATYP=C&_AUDT=A&_KS=zz&_OPM=A&_DATA=TRUE& ]]>
</QueryVal>
</RecAtt>
</ParentRec>
<ErrMsg ErrNbr="0" ErrStat="MSG">
<![CDATA[ Success ]]>
</ErrMsg>
</Report>
3) This time, we have to put the URL together (I’ve highlighted the applicable lines). Take the value of the cgi-dir and the executable attributes from the Report node. Add the CDATA value from the QueryBase node. Then add the CDATA value from the QueryVal node that is in the RecAtt node with an action attribute of “” (the one at the bottom). That gives us:
/cgi-lawson/getattachrec.exe?_OUT=XML&_PDL=PROD&_FN=APINVOICE&_IN=APISET1&K1=0120&K2=++++20840&K3=3694823&K4=000&K5=9999&_ATYP=C&_AUDT=A&_KS=zz&_OPM=A&_DATA=TRUE&
The result of which is our comment detail, and it looks like this:
<?xml version="1.0" encoding="ISO-8859-1" ?>
<Report cgidir="/cgi-lawson/" executable="getattachrec.exe" productline="PROD" filename="APINVOICE" token="Token" keynbr="KeyNbr">
<QueryBase exepath="/cgi-lawson/getattachrec.exe">
<![CDATA[ _OUT=XML&_PDL=PROD&_FN=APINVOICE&_IN=APISET1& ]]>
</QueryBase>
<WinTitle>
<![CDATA[ <NULL> ]]>
</WinTitle>
<ParentRec>
<QueryVal>
<![CDATA[ _AK=yEXb ]]>
</QueryVal>
<RecAtt Action="">
<AttName>
<![CDATA[ Comment Name ]]>
</AttName>
<AttData>
<![CDATA[ Comment Text%0A ]]>
</AttData>
<QueryVal>
<![CDATA[ K1=0120&K2=++++20840&K3=3694823&K4=000&K5=9999&_ATYP=C&_AUDT=A&_KS=zz&_OPM=A&_DATA=TRUE& ]]>
</QueryVal>
</RecAtt>
</ParentRec>
<ErrMsg ErrNbr="0" ErrStat="MSG">
<![CDATA[ Success ]]>
</ErrMsg>
</Report>
The comment data is in the AttData node. Wasn’t that easy?
Note: Newlines in the AttData node will be represented by %0A.
As for which of the three methods you use, that’s up to you and your requirements. Chances are, you’ll wind up using a combination. I like running a SQL query to get the list of comments, but actually use the ListAttachments to retrieve the data. This saves on the heartache of trying to reconstruct the comment from the SQL and it limits how much work we have to do because we’ll only try to retrieve comments for those records that actually have them.
Comments and Portal
You can add a Comment popup to Design Studio and custom portal pages. This is especially useful when the users are not actually on the forms they might want to view comments for. An example of this might be in an inbasket view. You could give the users a link to view the comments on an invoice without having to leave the inbasket.
The function is:
top.portalObj.drill.doAttachment(window, "lawformRestoreCallback", idaCall, 'CMT');
The idaCall is a URL and is the same as we built in #1 above. You should leave everything else the same.
Anything else you “need” to know? Leave a comment and I’ll answer if I can.
HTH